TL;DR: This article discusses the differences between async Task and async void, and how async void methods and async void lambdas, used outside the DispatcherSynchronizationContext, can crash the process if exceptions are not handled.

In the first part of the series, we discussed the behavior of async methods. The second part discusses how async Task and async void methods can differ in behavior, while seemingly being similar.

Authoring Async Methods

Async methods can be authored in three different ways:

async Task MyMethod() { }

which creates a method that can be awaited, but does not return any value,

async Task<T> MyReturningMethod { return default(T); }

which creates a method that can be awaited, and returns a value of the type T,

async void MyFireAndForgetMethod() { }

which is allows for fire and forget methods, and cannot be awaited.

You may be wondering why there are two ways to declare a void returning method. Read on.

async void methods

async void methods exist for the single purpose of being used as an event handler. More specifically, it has been introduced to support UI elements event handlers, nothing else.

It allows writing simple async methods such as this one:

private async void Button1_Click(object sender, EventArgs args) { }

All Microsoft provided events respect a BCL guideline for which all .NET event delegates return void, which is in part why the async Task declaration cannot not be used.

An async void method is handled differently under the hood by the compiler. Task returning async methods are managed by the AsyncTaskMethodBuilder class, whereas async void methods are managed by the AsyncVoidMethodBuilder class.

Both builders handle the synchronization context capture the same way, where the ambient context is reused to execute each block of code between the awaits of an async method.

This means than in normal circumstances, the code executes the same way in both Task and void returning methods.

But there is one fundamental difference between async Task and async void: Exceptions handling.

async Task methods and Exceptions

When executed, this program will not end until a key is pressed. The exception will be stored in an anonymous Task instance, because the return value of call to Test is never awaited, nor stored in a variable.

It is a pretty bad practice to write this kind of code, because the exception is essentially silent, and may hide a bug. The compiler is a bit helpful in that regard to avoid writing that kind of code, and will provide you with the following warning message:

warning CS4014: Because this call is not awaited, execution of the current method continues before the call is completed. Consider applying the 'await' operator to the result of the call.

As a best practice, you should never have this warning anywhere in your code, and handle exceptions properly.

TPL unhandled exceptions

The TPL, which used behind async methods, handles exceptions differently than the ThreadPool, and unobserved exceptions will be thrown in the UnobservedTaskException event.

First, you’ll notice that the compiler warning that was present with async Task is no longer shown.

Second, when run, the whole process crashes.

The reason behind this is the Synchronization Context used by the AsyncVoidMethodBuilder, being none in this example. When there is no ambient Synchronization Context, any exception that is unhandled by the body of an async void method is rethrown on the ThreadPool.

To make the matter a bit more difficult, this event is not available in Metro apps, because the AppDomain class has been removed.

Async lambdas

Unless you consider yourself as reliable enough to never forget to place a try catch block in all async void methods, you should never use an async void method anywhere but in UI event handlers, such as behind button clicks and alike.

You could make a code review and search for all async void methods using a find and replace... but you won’t find this one:

new List<int>().ForEach(async i => { throw new Exception(); });

The method signature for List<T>.ForEach is :

public void ForEach(Action<T> action);

for which it is valid to provide and async void lambda, putting the process at risk of termination.

This kind of code is very easy to write when moving stateful code to async-only APIs, and you may crash your process without being warned that you’re writing dangerous code.

An other interesting fact about these async void lambdas is that the compiler, when facing these two overloads :

async void is for UI Event Handlers Only

When writing UI event handlers, async void methods are somehow painless because exceptions are treated the same way found in non-async methods; they are thrown on the Dispatcher. There is a possibility to recover from such exceptions, with is more than correct for most cases.

Outside of UI event handlers however, async void methods are somehow dangerous to use and may not that easy to find.

This is why I’ve been using a custom Static Analysis (fxcop) rule that prevents the use of the AsyncVoidMethodBuilder class in my apps, effectively banning both explicit and implicit async void methods.

Most of this code is using an MVVM approach where ICommand data-binding is used, therefore limiting the use of any async void methods.

Cloud

About me

My name is Jerome Laban, I am a Software Architect, C# MVP and .NET enthustiast from Montréal, QC. You will find my blog on this site, where I'm adding my thoughts on current events, or the things I'm working on, such as the Remote Control for Windows Phone.