Async - Handling multiple Exceptions
Async - Handling multiple Exceptions
the SDP conference was ended a few week ago and I finally find a time to write some comments.
It was a very successful conference, the feedback and evaluations, of most sessions scored higher than 4.5, some well-over it. For example, one workshop had a perfect 5 / 5 score, and two other workshops scored 4.92 / 5 and 4.9 / 5. The highest score for a breakout session was 9.39 / 10, which is the highest score we’ve seen to date. my score was 4.90 / 5.
but this post isn't about scores, it is about fixing a sample that I was showing at the SDP about exception handling within the async / await context.

when you do use the great async / await syntax you may run into a problem while trying to handle multiple exception which was thrown from async operations (it can happens in cases like parent child relationship or when using WhenAll API).
by design the catch closure will handle the first exception and ignore all the other, you can read about it in here.
the following code will catch a single NullReferenceException or ArgumentException exception (the AggregateException will be ignored):
Code Snippet
- try
- {
- await Task.Factory.StartNew(() =>
- {
- Task.Factory.StartNew(() =>
- { throw new NullReferenceException(); },
- TaskCreationOptions.AttachedToParent);
- Task.Factory.StartNew(() =>
- { throw new ArgumentException(); },
- TaskCreationOptions.AttachedToParent);
- });
- }
- catch (AggregateException ex)
- {
- // this catch will never be target
- }
- catch (Exception ex)
- {
- Console.WriteLine("## {0} ##", ex.GetType().Name);
- }
- }
if you do want to catch the AggregateException you can use the following work around :
Code Snippet
- try
- {
- Task t = Task.Factory.StartNew(() =>
- {
- Task.Factory.StartNew(() =>
- { throw new NullReferenceException(); },
- TaskCreationOptions.AttachedToParent);
- Task.Factory.StartNew(() =>
- { throw new ArgumentException(); },
- TaskCreationOptions.AttachedToParent);
- });
- await t.ContinueWith(tsk => { if (tsk.Exception != null) throw tsk.Exception; });
- }
- catch (AggregateException ex)
- {
- foreach (var exc in ex.Flatten().InnerExceptions)
- {
- Console.WriteLine(exc.GetType().Name);
- }
- }
the difference is at line 12, I'm using a continuation that re-throw the AggregateException.
because this is a fairly common practice you may better wrap it within an extension method like the following one:
Code Snippet
- public static Task ThrowMultiple(this Task t)
- {
- return t.ContinueWith(tsk => { if (tsk.Exception != null) throw tsk.Exception; });
- }
-
- public static Task<T> ThrowMultiple<T>(this Task<T> t)
- {
- return t.ContinueWith(tsk =>
- {
- if (tsk.Exception != null)
- throw tsk.Exception;
- return tsk.Result;
- });
- }
you can use it as follow:
Code Snippet
- try
- {
- await Task.Factory.StartNew(() =>
- {
- Task.Factory.StartNew(() =>
- { throw new NullReferenceException(); },
- TaskCreationOptions.AttachedToParent);
- Task.Factory.StartNew(() =>
- { throw new ArgumentException(); },
- TaskCreationOptions.AttachedToParent);
- }).ThrowMultiple();
- }
- catch (AggregateException ex)
- {
- foreach (var exc in ex.Flatten().InnerExceptions)
- {
- Console.WriteLine(exc.GetType().Name);
- }
- }
Summary
catching a single exception within an async method is a design decision,
but you may want to override this decision in some scenarios.
I hope that in a future release of .NET framework Microsoft consider to throw an AggregateException when the compiler will detect that the developer was adding an AggregateException catch block.
in the meantime you can use the extension methods.
you should not forget to catch a non AggregateException if you have any code before the await (this code will run synchronously).