Click here to Skip to main content
15,861,168 members
Articles / All Topics

Living with async

Rate me:
Please Sign up or sign in to vote.
5.00/5 (1 vote)
7 Jun 2012CPOL3 min read 9.3K   5   1
Living with async

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:

Image 1

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.

C#
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:

C#
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.

C#
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.

This article was originally posted at http://softwarebytes.blogspot.com/feeds/posts/default

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Architect
Greece Greece
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
GeneralMy vote of 5 Pin
ring_029-Nov-12 17:14
ring_029-Nov-12 17:14 
Just awesome!

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.