Matthew Watson
Matthew Watson

Reputation: 109822

How to get the original exception when using ContinueWith()?

Consider the following code:

using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace Demo
{
    static class Program
    {
        static void Main()
        {
            var tasks = new Task[1];

            tasks[0] = Task.Run(() => throwExceptionAfterOneSecond())
                .ContinueWith(task => {
                    Console.WriteLine("ContinueWith()"); }, TaskContinuationOptions.NotOnFaulted);

            try
            {
                Task.WaitAll(tasks);
            }

            catch (AggregateException ex)
            {
                Console.WriteLine("Exception received: " + ex.InnerExceptions.Single().Message);
            }
        }

        static void throwExceptionAfterOneSecond()
        {
            Thread.Sleep(1000);
            throw new InvalidOperationException("TEST");
        }
    }
}

This yields the following output:

Exception received: A task was canceled.

My question is simple: How do I get at the original InvalidOperationException("TEST"); rather than a System.Threading.Tasks.TaskCanceledException?

Note that if you remove the .ContinueWith() part, this works as I expected and the output in that case is Exception received: TEST.

(Also note that this example is using .Net 4.5, but the original code must use .Net 4.0)


SOLUTION

Thanks to the answers, this is now working. I chose the following solution - I needed to wait on both the original task AND the continuation task:

using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace Demo
{
    static class Program
    {
        static void Main()
        {
            var tasks = new Task[2];

            tasks[0] = Task.Run(() => throwExceptionAfterOneSecond());

            tasks[1] = tasks[0].ContinueWith(task => {
                if (task.Status == TaskStatus.RanToCompletion)
                    Console.WriteLine("ContinueWith()"); });
            try
            {
                Task.WaitAll(tasks);
            }

            catch (AggregateException ex)
            {
                Console.WriteLine("Exception received: " + ex.InnerExceptions.Single().Message);
            }

            Console.WriteLine("Done.");
        }

        static void throwExceptionAfterOneSecond()
        {
            Thread.Sleep(1000);
            throw new InvalidOperationException("TEST");
        }
    }
}

Upvotes: 5

Views: 1185

Answers (3)

Fermin
Fermin

Reputation: 36111

If you need to catch the exception in the catch statement you could rethrow from inside the continue. E.g.

tasks[0] = Task.Run(() => throwExceptionAfterOneSecond())
    .ContinueWith(task =>
    {
        if (task.IsFaulted)
        {
            // Throw the inner exception
            throw task.Exception.InnerException;
        }

        Console.WriteLine("ContinueWith()");
    });

Upvotes: 3

bit
bit

Reputation: 4487

You could split up the ContinueWith part in to such a way that they are separate in case of exception and separate in case of sucess. here's an example:

var tasks = new Task[1];
tasks[0] = Task.Run(() => throwExceptionAfterOneSecond());

// For error handling.
tasks[0].ContinueWith(task =>
    {
        // Your logic to handle the exception goes here

        // Aggregate exception
        Console.WriteLine(task.Exception.Message);

        // Inner exception, which is your custom exception
        Console.WriteLine(task.Exception.InnerException.Message);
    },
    TaskContinuationOptions.OnlyOnFaulted);

// If it succeeded.
tasks[0].ContinueWith(task => 
{
    // success
    Console.WriteLine("ContinueWith()");
},TaskContinuationOptions.OnlyOnRanToCompletion);

Upvotes: 1

usr
usr

Reputation: 171246

You need to store a reference to Task.Run(() => throwExceptionAfterOneSecond()) so that you can later examine it's Exception property. This is the only task that faulted. Examining any other task will not provide that exception.

I'd also not rely on TaskContinuationOptions.NotOnFaulted because this pretty much forces using exceptions for control flow. It is hard to wait for a non-normally completed task without an exception being thrown.

.ContinueWith(task => {
     if (task.Status == RanToCompletion) Console.WriteLine("ContinueWith()");
}

Upvotes: 3

Related Questions