The Perfect Recipe to Shoot Yourself in The Foot - Ending up with a Deadlock Using the C# 5.0 Asynchronous Language Features

Let's see how we can end up with a deadlock using the C# 5.0 asynchronous language features (AKA async/await) in our ASP.NET applications and how to prevent these kinds of scenarios.
2012-06-02 23:01
Tugberk Ugurlu


shoot_yourself_in_the_footI just finished The zen of async: Best practices for best performance talk of Stephen Toub’s on //Build and learnt 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 watched this talk. The talk that Toub has given was mostly about how you create better reusable libraries with asynchronous language features but there are lots of great information concerning application level code.

When you are awaiting on a method with await keyword, compiler generates bunch of code in behalf of you. One of the purposes of this action 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 consumptions. 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:

build_task

This is a 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.

public 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. Solution to this problem is so simple: don’t use the Wait method. The code you should be writing should be as follows:

public 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.

public 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.

Resources



Comments

Jeff
by Jeff on Wednesday, Jul 11 2012 15:41:55 +03:00

One very valid reason to use Task.Wait or Task.Result (which waits):  you need to use an Async API in a server or some other environment where you can't always just return to an event loop.  I agree it is a Bad Idea to call Wait in an async event handler of a UI application.  Thanks for the great diagram illustrating exactly what is happening.

ConfigureAwait(false) is often the solution but callers then need to be aware that the continuation may come on another thread.

Tugberk
by Tugberk on Thursday, Jul 12 2012 07:07:15 +03:00

@Jeff

The diagram doesn't belong to me. I took it from The zen of async: Best practices for best performance talk of Stephen Toub.

New Comment