Exception Handling with the Task Parallel Library
Thursday, October 21, 2010 – 11:08 AMOne of the attendees at the p&p Symposium had some questions about how exceptions are handled in the Task Parallel Library. This is covered both in the book and in the MSDN library (references below).
But… Here’s a quick summary with code examples.
Observe by waiting
WaitAny/WaitAll will throw an AggregateException if the task being waited on threw:
1: var observedTask1 = Task.Factory.StartNew(() =>
2: {
3: Console.WriteLine("Task 1 throwing...");
4: throw new ArgumentException("Task 1");
5: });
6:
7: try
8: {
9: Task.WaitAll(observedTask1); // t1 is Observed here!
10: }
11: catch (AggregateException ex)
12: {
13: foreach (var e in ex.Flatten().InnerExceptions)
14: Console.WriteLine("Observed exception: " + e.Message);
15: }
Observe with a continuation task
A continuation task can examine the antecedent task’s Exception property and thereby observe it:
1: var observedTask2 = Task.Factory.StartNew(() =>
2: {
3: Console.WriteLine("Task 2 throwing...");
4: throw new ArgumentException("Task 2");
5: });
6: var observerTask = observedTask2.ContinueWith((t) =>
7: {
8: foreach (var e in t.Exception.Flatten().InnerExceptions)
9: Console.WriteLine("Observed exception: " + e.Message);
10: },
11: TaskContinuationOptions.OnlyOnFaulted);
12:
13: Task.WaitAll(observerTask);
Observe with the unobserved exception handler
Use the scheduler’s UnobservedTaskException handler to to process exceptions and mark them as observed:
1: TaskScheduler.UnobservedTaskException += (sender, args) =>
2: {
3: foreach (var e in args.Exception.Flatten().InnerExceptions)
4: Console.WriteLine("Observed exception: " + e.Message);
5: args.SetObserved();
6: };
7:
8: var observedTask3 = Task.Factory.StartNew(() =>
9: {
10: Console.WriteLine("Task 3 throwing...");
11: throw new ArgumentException("Task 3");
12: });
13:
14: Thread.Sleep(3000);
15: observedTask3 = null;
16: GC.Collect();
This is a last line of defense. It may be appropriate for handling completely unexpected exceptions from plugins for example. The handler runs on the finalizer thread so it’s probably too late for your application to really recover from this.
What happens when you don’t observe an exception?
The short answer is… VERY BAD THINGS. You’ll see an error like this…
A Task’s exception(s) were not observed either by Waiting on the Task or accessing its Exception property. As a result, the unobserved exception was rethrown by the finalizer thread.
The call stack will be very unhelpful because the exception was observed and thrown on the GC’s finializer thread.
mscorlib.dll!System.Threading.Tasks.TaskExceptionHolder.Finalize() + 0x29c bytes
[Native to Managed Transition]
This makes it very hard to figure out which of your tasks caused the problem. The best way to resolve issues like this is to debug with break on unhandled exceptions and/or break when thrown set to see what’s being thrown and where before it gets cleaned up on the finalizer thread.
References
Parallel Tasks, Handling Exceptions (from our book)
Exception Handling (MSDN Library)
4 Responses to “Exception Handling with the Task Parallel Library”
The following extension method is sometimes useful:
public static Task WithExceptionHandler(
this Task source,
Action handler)
{
source.ContinueWith(t =>
handler(t.Exception),
TaskContinuationOptions.OnlyOnFaulted);
return source;
}
-Duarte
By Duarte on Oct 21, 2010