6
votes

I have read a lot on how to handle exceptions in TPL but don't really understand.

Lets take this example code:

var task1 = new Task(() => { throw new Exception("Throw 1"); });
var task2 = task1.ContinueWith(t => Console.WriteLine("Catch 1:{0}", t.Exception.Message), 
                              TaskContinuationOptions.OnlyOnFaulted);
var task3 = task2.ContinueWith(t => Console.WriteLine("Continuation"));

task1.Start();
try {
    task1.Wait();
}
catch (Exception ex) {
    Console.WriteLine("Wait Exception: {0}", ex.Message);
}

I expected this to print

Catch 1
Continuation

But I get

Catch 1
Continuation
Wait Exception

This means that the exception is still considered unhandled when the task completes and the task finalizer will eventually tear down the application.

How do I handle the exception inside the continuation so the finalizer will not throw? At the same time I want the task to remain in the faulted state so wrapping the task in try/catch will not work.


The background is that I want to implement the async event pattern as specified here but with error handling. My complete code looks like this

public IAsyncResult Begin(AsyncCallback callback, object state, Action action) {
    var task1 = new Task(action);
    var task2 = task1.ContinueWith(t => HandleException(t.Exception), 
                                   TaskContinuationOptions.OnlyOnFaulted);
    if (callback != null) {
        var task3 = task2.ContinueWith(t => callback(t),
                                      TaskScheduler.FromCurrentSynchronizationContext());
        var task4 = task3.ContinueWith(t => HandleException(t.Exception), 
                                       TaskContinuationOptions.OnlyOnFaulted);
    }

    task1.Start();

    return task;
}
1
I thought about calling t.Wait() in a try in the continuation and ignoring the exception. But that won't work, because the continuation may be executed after the other Wait() throws.svick

1 Answers

3
votes

You do your wait on the task that fails, and if you read the documentation on Task.Wait carefully you will see that wait will rethrow the exception in this case.

But if you wait on your task3 everything should work as expected.

Of course you should keep this in mind:

When you use the OnlyOnFaulted option, it is guaranteed that the Exception property in the antecedent is not null. You can use that property to catch the exception and see which exception caused the task to fault. If you do not access the Exception property, the exception will go unhandled. Also, if you attempt to access the Result property of a task that has been canceled or has faulted, a new exception will be raised.

(Reference here)

And finally yet another good source on How to handle exceptions thrown by tasks

I hope this helps.