Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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");
        }
    }
}
like image 817
Matthew Watson Avatar asked Apr 28 '16 10:04

Matthew Watson


1 Answers

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()");
}
like image 73
usr Avatar answered Oct 28 '22 20:10

usr