Reputation: 109822
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
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
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
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