This is the sixth part of the Async Wandering series. For your convenience you can find other parts in the table of contents in Part 1 – Why creating Form from WinForms in unit tests breaks async?
Let’s take the following code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
using System; using System.Threading.Tasks; namespace AggregateException { class Program { static void Main(string[] args) { CreateAndAwait().Wait(); } static async Task CreateAndAwait() { await CreateTask(); } static Task CreateTask() { return Task.Factory.StartNew(() => { Task.Factory.StartNew(() => { throw new Exception("FIRST TASK EXCEPTION!!!"); }, TaskCreationOptions.AttachedToParent); Task.Factory.StartNew(() => { throw new Exception("SECOND TASK EXCEPTION!!!"); }, TaskCreationOptions.AttachedToParent); Console.WriteLine("Attached both children"); }); } } } |
We create a task and add two child tasks to it, both of them throwing exceptions. What is the output?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
Attached both children Unhandled Exception: System.AggregateException: One or more errors occurred. ---> System.AggregateException: One or more errors occurred. ---> System.Exception: SECOND TASK EXCEPTION!!! at AggregateException.Program.<>c.<CreateTask>b__3_2() in C:\Users\afish\Desktop\msp_windowsinternals\AggregateException\Program.cs:line 35 at System.Threading.Tasks.Task.InnerInvoke() at System.Threading.Tasks.Task.Execute() --- End of inner exception stack trace --- at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at System.Runtime.CompilerServices.TaskAwaiter.GetResult() at AggregateException.Program.<CreateAndAwait>d__2.MoveNext() in C:\Users\afish\Desktop\msp_windowsinternals\AggregateException\Program.cs:line 26 --- End of inner exception stack trace --- at System.Threading.Tasks.Task.ThrowIfExceptional(Boolean includeTaskCanceledExceptions) at System.Threading.Tasks.Task.Wait(Int32 millisecondsTimeout, CancellationToken cancellationToken) at System.Threading.Tasks.Task.Wait() at AggregateException.Program.Main(String[] args) in C:\Users\afish\Desktop\msp_windowsinternals\AggregateException\Program.cs:line 10 |
So we lost one exception. This is a case when introducing async
changes semantics and doesn’t integrate with the platform nicely. How to fix that?
With this:
1 2 3 4 5 6 7 |
static Task CreateAndWait() { Task action = CreateTask(); Task faulted = action.ContinueWith(p => Console.WriteLine(p.Exception), TaskContinuationOptions.OnlyOnFaulted); Task succeeded = action.ContinueWith(r => { }, TaskContinuationOptions.OnlyOnRanToCompletion); return Task.WhenAll(faulted, succeeded); } |
This correctly prints out both exceptions, however, we need to use normal TPL methods.