This is the seventh 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 this code:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
using System; using System.Threading.Tasks; namespace UnobservedException { class Program { static void Main(string[] args) { Test(); } public static async Task Test() { throw new Exception(); } } } |
It doesn’t print any exception because we don’t await the task. Can we do something about it?
Let’s use the UnobservedTaskException
handler:
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 UnobservedException { class Program { static void Main(string[] args) { TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException; Test(); Console.ReadLine(); GC.Collect(); GC.WaitForPendingFinalizers(); } static void TaskScheduler_UnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e) { Console.WriteLine("Unobserved exception!"); Console.WriteLine(e.Exception); } public static async Task Test() { throw new Exception(); } } } |
You can now verify that it prints out the exception correctly after you press enter. When GC notices there is a task with an exception on it, it handles it and calls the delegate.
But! If you take a look at Task source it has no finalizer. How does it work?
If you decompile async method, you can see this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
// UnobservedException.Program.<Test>d__2 // Token: 0x06000006 RID: 6 RVA: 0x000020E0 File Offset: 0x000002E0 void IAsyncStateMachine.MoveNext() { int num = this.<>1__state; try { throw new Exception(); } catch (Exception exception) { this.<>1__state = -2; this.<>t__builder.SetException(exception); } } |
So there is some SetException
method. It creates a TaskExceptionHolder under the hood. That type has a finalizer and does the following:
1 2 3 |
AggregateException ex4 = new AggregateException(Environment.GetResourceString("TaskExceptionHolder_UnhandledException"), this.m_faultExceptions); UnobservedTaskExceptionEventArgs unobservedTaskExceptionEventArgs = new UnobservedTaskExceptionEventArgs(ex4); TaskScheduler.PublishUnobservedTaskException(this.m_task, unobservedTaskExceptionEventArgs); |
So it calls the handler.