I was watching once again The zen of async: Best practices for best performance talk of Stephen Toub’s on //Build and I decided I should blog about how easy is to end up with a deadlock while writing asynchronous code with new C# 5.0 language features (AKA async/await). Here is the quick background first which I was aware of before I watch this talk. The talk that Toub has given was mostly about how you create better reusable libraries with asynchronous language features but there is lots of great information concerning application level code.
When you are awaiting on a method with await
keyword, compile generates a bunch of code of your behalf. One of the purposes of this is to handle synchronization with the UI thread. The key component of this feature is the SynchronizationContext.Current which gets the synchronization context for the current thread. SynchronizationContext.Current
is populated depending on the environment you are in. For example, if you are on a WPF application, SynchronizationContext.Current
will be a type of DispatcherSynchronizationContext. In an ASP.NET application, this will be an instance of AspNetSynchronizationContext
which is not publicly available and not meant for external consumption. SynchronizationContext
has virtual Post method which dispatches an asynchronous message to a synchronization context when overridden in a derived class. When you give this method a delegate, this delegate will be marshaled back to the UI thread and invoked on that UI thread.
The GetAwaiter
method (yes, "waiter" joke is proper here) of Task
looks up for SynchronizationContext.Current
. If current synchronization context is not null
, the continuation that gets passed to that awaiter will get posted back to that synchronization context. This feature is a great feature when we are writing application level code but it turns out that if we are not careful enough, we might end up with a deadlock because of this. Here is how:
This is part of the slide from that talk and shows how we can end up with a deadlock. Task has a method named Wait
which hangs on the task till it completes. This method is an evil method in my opinion but I am sure that there is a good reason why it is provided. You never ever want to use this method. But, assume that you did use it on a method which returns a Task
or Task<T>
for some T
and uses await
inside it to await another task, you will end up with a deadlock if you are not careful. The picture explains how that can happen but let’s recap with an example.
class HomeController : Controller
{
public ActionResult Index()
{
doWorkAsync().Wait();
return View();
}
private async Task doWorkAsync()
{
await Task.Delay(500);
}
}
The above code has an ASP.NET MVC 4 asynchronous action method (I am using .NET 4.5 here) which consumes the private doWorkAsync
method. I used ASP.NET MVC here but everything applies for ASP.NET Web API asynchronous action methods as well. Inside the doWorkAsync
method, we used Delay
method of Task
to demonstrate, but this could be any method which returns a Task
or Task<T>
. Inside the Index
action method, we invoke the Wait
method on the task which we got from doWorkAsync
method to ensure that we won’t go further unless the operation completes and the interesting part happens right here. At that point, we block the UI thread at the same time. When eventually the Task.Delay
method completes in the threadpool, it is going to invoke the continuation to post back to the UI thread because SynchronizationContext.Current
is available and captured. But there is a problem here: the UI thread is blocked. Say hello to our deadlock!
When you run the application, you will see that the web page will never come back. The solution to this problem is so simple: don’t use the Wait
method. The code you should be writing should be as follows:
class HomeController : Controller
{
public async Task<ActionResult> Index()
{
await doWorkAsync();
return View();
}
private async Task doWorkAsync()
{
var task = Task.Delay(500);
}
}
But if you have to use it for some weird reasons, there is another method named ConfigureAwait
on Task
which you can configure not to use the captured context.
class HomeController : Controller
{
public ActionResult Index()
{
doWorkAsync().Wait();
return View();
}
private async Task doWorkAsync()
{
var task = Task.Delay(500);
await task.ConfigureAwait(continueOnCapturedContext: false);
}
}
Very nice little and tricky information but a lifesaver.
CodeProject
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.