eugeneK
eugeneK

Reputation: 11146

Inner threads exception handling

I have an object A from which i want to execute new thread with object's B method. I can use Task.CreateNew etc.. problem is i don't know how to handle exception in new thread.

Generally what i want is that inner thread with object's B method to throw exception which parent object A will catch and close it's execution, along with object B.

Is there any way to achieve it ?

In code below i don't have exception handling and master thread continues:

static void Main()
{
    Console.WriteLine("start");
    Task.Factory.StartNew(PrintTime, CancellationToken.None);

    for (int i = 0; i < 20; i++)
    {
        Console.WriteLine("master thread i={0}", i + 1);
        Thread.Sleep(1000);
    }
}

private static void PrintTime()
{
    for (int i = 0; i < 10; i++)
    {
        Console.WriteLine("inner thread i={0}",i+1);
        Thread.Sleep(1000);
    }

    throw new Exception("exception");
}

Upvotes: 1

Views: 1011

Answers (5)

Ryszard Dżegan
Ryszard Dżegan

Reputation: 25464

The below solution works regardless which thread throws an exception at first:

static void Main()
{
    Console.WriteLine("start");

    var innerCts = new CancellationTokenSource();
    Exception mainException = null;
    var mainThread = new Thread(() => SafeMainThread(innerCts, ref mainException));
    mainThread.Start();

    var innerTask = Task.Factory.StartNew(state => PrintTime(state),
                                          innerCts,
                                          innerCts.Token,
                                          TaskCreationOptions.LongRunning,
                                          TaskScheduler.Default);

    var innerFault = innerTask.ContinueWith(t => { Console.WriteLine("Inner thread caused " + t.Exception.InnerException.GetType().Name + ". Main thread is being aborted..."); mainThread.Abort(); },
                                            TaskContinuationOptions.OnlyOnFaulted);

    var innerCancelled = innerTask.ContinueWith(_ => Console.WriteLine("Inner thread cancelled."),
                                                TaskContinuationOptions.OnlyOnCanceled);

    var innerSucceed = innerTask.ContinueWith(_ => Console.WriteLine("Inner thread completed."),
                                              TaskContinuationOptions.OnlyOnRanToCompletion);

    try
    {
        innerTask.Wait();
    }
    catch (AggregateException)
    {
        // Ignore.
    }

    mainThread.Join();

    Console.ReadLine();
}

private static void SafeMainThread(CancellationTokenSource innerCts, ref Exception mainException)
{
    try
    {
        MainThread();
        Console.WriteLine("Main thread completed.");
    }
    catch (ThreadAbortException)
    {
        Console.WriteLine("Main thread aborted.");
    }
    catch (Exception exception)
    {
        Console.WriteLine("Main thread caused " + exception.GetType().Name + ". Inner task is being canceled...");

        innerCts.Cancel();
        mainException = exception;
    }
}

private static void MainThread()
{
    for (int i = 0; i < 20; i++)
    {
        Console.WriteLine("master thread i={0}", i + 1);
        Thread.Sleep(500);
    }
    throw new Exception("exception");
}

private static void PrintTime(object state)
{
    var cts = (CancellationTokenSource)state;

    for (int i = 0; i < 10; i++)
    {
        cts.Token.ThrowIfCancellationRequested();

        Console.WriteLine("inner thread i={0}", i + 1);
        Thread.Sleep(500);
    }

    throw new Exception("exception");
}

Upvotes: 1

Ryszard Dżegan
Ryszard Dżegan

Reputation: 25464

If you can't modify internals of main body, I don't know how to get fine-grained control on it. The solution I see at the moment is to wrap the main body into managed thread that allows to abort it if inner thread throws an exception:

static void Main()
{
    Console.WriteLine("start");

    var mainThread = new Thread(MainThread);
    mainThread.Start();

    var task = Task.Factory
                   .StartNew(PrintTime)
                   .ContinueWith(t => { Console.WriteLine("Inner thread caused exception. Main thread is being aborted."); mainThread.Abort(); },
                                 TaskContinuationOptions.OnlyOnFaulted);
    task.Wait();

    Console.WriteLine("Waiting for main thread to abort...");
    mainThread.Join();
    Console.WriteLine("Main thread aborted.");

    Console.ReadLine();
}

private static void MainThread()
{
    for (int i = 0; i < 20; i++)
    {
        Console.WriteLine("master thread i={0}", i + 1);
        Thread.Sleep(1000);
    }
}

private static void PrintTime()
{
    for (int i = 0; i < 10; i++)
    {
        Console.WriteLine("inner thread i={0}", i + 1);
        Thread.Sleep(1000);
    }

    throw new Exception("exception");
}

Upvotes: 0

Shakti Prakash Singh
Shakti Prakash Singh

Reputation: 2533

You can try and run an exception handler on the occurrence of exception. You can use a flag, I have used the exception for checking the exception occurence itself in the example:

    private static AggregateException exception = null;

    static void Main(string[] args)
    {
        Console.WriteLine("Start");

        Task.Factory.StartNew(PrintTime, CancellationToken.None).ContinueWith(HandleException, TaskContinuationOptions.OnlyOnFaulted);

        for (int i = 0; i < 20; i++)
        {
            Console.WriteLine("Master Thread i={0}", i + 1);
            Thread.Sleep(1000);
            if (exception != null)
            {
                break;
            }
        }

        Console.WriteLine("Finish");
        Console.ReadLine();
    }

    private static void HandleException(Task task)
    {
        exception = task.Exception;
        Console.WriteLine(exception);
    }

    private static void PrintTime()
    {
        for (int i = 0; i < 10; i++)
        {
            Console.WriteLine("Inner Thread i={0}", i + 1);
            Thread.Sleep(1000);
        }

        throw new Exception("exception");
    }

Upvotes: 0

Douglas
Douglas

Reputation: 54917

Retain a reference to your task instance, and call Wait on it when you're ready to process its result. Any unhandled inner exception that was thrown during the task's execution would be wrapped in an AggregateException that is, in turn, thrown from the Wait method.

static void Main()
{
    Console.WriteLine("start");
    Task task = Task.Factory.StartNew(PrintTime, CancellationToken.None);

    for (int i = 0; i < 20; i++)
    {
        Console.WriteLine("master thread i={0}", i + 1);
        Thread.Sleep(1000);

        // Stop iterating in case of unhandled exception in inner task.
        if (task.Status == TaskStatus.Faulted)
            break;
    }

    try
    {
        task.Wait();
    }
    catch (AggregateException ae) 
    {
        ae.Handle((x) =>
        {
             Console.WriteLine("Exception: " + x.ToString());
        });
    }
}

Upvotes: 2

Ryszard Dżegan
Ryszard Dżegan

Reputation: 25464

Try to intercept inner exception and check its value in the main loop as if would be a cancellation request:

static void Main()
{
    Console.WriteLine("start");

    try
    {
        AggregateException innerException = null;

        Task.Factory.StartNew(PrintTime, CancellationToken.None)
                    .ContinueWith(t => innerException = t.Exception, TaskContinuationOptions.OnlyOnFaulted);

        for (int i = 0; i < 20; i++)
        {
            if (innerException != null)
                throw innerException;

            Console.WriteLine("master thread i={0}", i + 1);
            Thread.Sleep(1000);
        }
    }
    catch (AggregateException)
    {
        Console.WriteLine("Inner thread caused exception. Main thread handles that exception.");
    }
}

Upvotes: 0

Related Questions