TAP global exception handler
This is actually a good question, if I understood it correctly. I initially voted to close it, but now retracted my vote.
It is important to understand how an exception thrown inside an async Task
method gets propagated outside it. The most important thing is that such exception needs to be observed by the code which handles the completion of the task.
For example, here is a simple WPF app, I'm on NET 4.5.1:
using System;
using System.Threading.Tasks;
using System.Windows;
namespace WpfApplication_22369179
{
public partial class MainWindow : Window
{
Task _task;
public MainWindow()
{
InitializeComponent();
AppDomain.CurrentDomain.UnhandledException +=
CurrentDomain_UnhandledException;
TaskScheduler.UnobservedTaskException +=
TaskScheduler_UnobservedTaskException;
_task = DoAsync();
}
async Task DoAsync()
{
await Task.Delay(1000);
MessageBox.Show("Before throwing...");
GCAsync(); // fire-and-forget the GC
throw new ApplicationException("Surprise");
}
async void GCAsync()
{
await Task.Delay(1000);
MessageBox.Show("Before GC...");
// garbage-collect the task without observing its exception
_task = null;
GC.Collect(GC.MaxGeneration, GCCollectionMode.Forced);
}
void TaskScheduler_UnobservedTaskException(object sender,
UnobservedTaskExceptionEventArgs e)
{
MessageBox.Show("TaskScheduler_UnobservedTaskException:" +
e.Exception.Message);
}
void CurrentDomain_UnhandledException(object sender,
UnhandledExceptionEventArgs e)
{
MessageBox.Show("CurrentDomain_UnhandledException:" +
((Exception)e.ExceptionObject).Message);
}
}
}
Once ApplicationException
has been thrown, it goes unobserved. Neither TaskScheduler_UnobservedTaskException
nor CurrentDomain_UnhandledException
gets invoked. The exception remains dormant until the _task
object gets waited or awaited. In the above example it never gets observed, so TaskScheduler_UnobservedTaskException
will be invoked only when the task gets garbage-collected. Then this exception will be swallowed.
The old .NET 4.0 behavior, where the AppDomain.CurrentDomain.UnhandledException
event gets fired and the app crashes, can be enabled by configuring ThrowUnobservedTaskExceptions
in app.config
:
<configuration>
<runtime>
<ThrowUnobservedTaskExceptions enabled="true"/>
</runtime>
</configuration>
When enabled this way, AppDomain.CurrentDomain.UnhandledException
will still be fired after TaskScheduler.UnobservedTaskException
when the exception gets garbage-collected, rather than on the spot where it thrown.
This behavior is described by Stephen Toub in his "Task Exception Handling in .NET 4.5" blog post. The part about task garbage-collection is described in the comments to the post.
That's the case with async Task
methods. The story is quite different for async void
methods, which are typically used for event handlers. Let's change the code this way:
public MainWindow()
{
InitializeComponent();
AppDomain.CurrentDomain.UnhandledException += CurrentDomain_UnhandledException;
TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException;
this.Loaded += MainWindow_Loaded;
}
async void MainWindow_Loaded(object sender, RoutedEventArgs e)
{
await Task.Delay(1000);
MessageBox.Show("Before throwing...");
throw new ApplicationException("Surprise");
}
Because it's async void
there's no Task
reference to hold on to (so there's nothing to be possibly observed or garbage-collected later). In this case, the exception is thrown immediately on the current synchronization context. For a WPF app, Dispatcher.UnhandledException
will be fired first, then Application.Current.DispatcherUnhandledException
, then AppDomain.CurrentDomain.UnhandledException
. Finally, if none of these events are handled (EventArgs.Handled
is not set to true
), the app will crash, regardless of the ThrowUnobservedTaskExceptions
setting. TaskScheduler.UnobservedTaskException
is not getting fired in this case, for the same reason: there is no Task
.
EDITED as per @Noseration's comment
In .NET 4.5 in async
code you can handle unobserved exceptions by registering a handler for the TaskScheduler.UnobservedTaskException
event. An exception is deemed unobserved if you do not access the Task.Result
, Task.Exception
properties and you do not call Task.Wait
.
After the unobserved exception reaches the TaskScheduler.UnobservedTaskException
event handler, the default behaviour is to swallow this exception so the program does not crash. This behaviour can be changed in the configuration file by adding the following:
<configuration>
<runtime>
<ThrowUnobservedTaskExceptions enabled="true"/>
</runtime>
</configuration>
Binding an event to the AppDomain.CurrentDomain.FirstChanceException
will guarantee you that your exception will be caught. As @Noseratio pointed out, you'll be notified of every exception in your application, even if the exception is handled gracefully within a catch block and the application continues on.
However, I still see this event being useful for at least capturing the last few exceptions thrown before an application halted or perhaps some other debugging scenario.
If you want to protect yourself against this
string x = await DoSomethingAsync();
My advice to you is, don't do that, add a try catch block :-)
You can always do the following to handle the exception using Application.DispatcherUnhandledException
method. Of course it would be given to you inside a TargetInvocationException
and might not be as pretty as other methods. But it works perfectly fine
_executeTask = executeMethod(parameter);
_executeTask.ContinueWith(x =>
{
Dispatcher.CurrentDispatcher.Invoke(new Action<Task>((task) =>
{
if (task.Exception != null)
throw task.Exception.Flatten().InnerException;
}), x);
}, TaskContinuationOptions.OnlyOnFaulted);