Don’t deadlock with async and await

Posted by Filip Ekberg on April 3 2013 4 Comments

Deadlocking is really something you need to avoid and in case you don’t know what a deadlock is here’s a great illustration of a “real life deadlock”:

deadlock

Basically what has happened here is that all the roads are full with cars and all the cars try to cross the road at the same time. Let’s translate this into computer terms; the cars in this case are the threads and the cross-over is the “thing” that handles these threads. In the illustration above all the cars have driven into the cross-over at the same time and they can’t really back up, hence there’s a deadlock and there’s no way to go.

What happens in a computer program when you get a deadlock is that it freezes and there’s no where to go because all paths are occupied or waiting for something to finish. Let’s say that process X waits for process Y and process Y waits for process X and both of these lock up the GUI thread, this means that the application will die. Hence deadlocking is something you want to avoid.

Normally you solve this by introducing locking and semaphores. As discussed in the article linked above (where I got the very nice illustration) a semaphore can be seen as a traffic light which handles how the cross-over is loaded with cars.

A while back I wrote an article called “Avoid shooting yourself in the foot with Tasks and Async”, I suggest that you should always return a Task from your asynchronous methods and you really should. What I am about to tell you below though is what you should avoid when doing this.

When a method is marked as asynchronous and the await-part is reached, the method will “exit” and return the “awaiting Task“, which means it’s not the Task that runs inside the method but in fact a Task that keeps track of the status of the asynchronous operation.

Let’s look at a basic code sample!

Consider that you have the following basic asynchronous method, all it does is that it waits for 2 seconds and then prints something to the debug window:

private async Task RunAsync()
{
    var run = Task.Factory.StartNew(() => {
        Thread.Sleep(2000);
    });

    await run;

    Debug.WriteLine("Execution done!");
}

Once await is reached, what will happen? A Task will be returned, but which one? Not the one named run! A Task that keeps track on the state machine will be returned.

Now what happens if we call this method on the GUI thread and asks to wait for it to finish? Calling Wait freezes the current thread and since we are on the GUI thread this will freeze the GUI thread, but for how long? When is Wait happy enough to proceed? In fact it will wait for the asynchronous task that handles the state machine to give it a signal that it’s now ready.

However that Task can never be marked as done until the entire method has been completed. Which means that it needs to access the GUI thread again, since we’re back on the calling thread (GUI thread in this case) after await!

This means that all we have to do in order to deadlock is this:

RunAsync().Wait();

I hope that makes sense to you and gives you an insight into what really happens when you use async and await. As with everything: use it wisely and know what it is that you’re doing.

I’d love to hear about your deadlocking stories!

Vote on HN

4 Responses to Don’t deadlock with async and await

  1. Pingback: Dew Drop – April 4, 2013 (#1,520) | Alvin Ashcraft's Morning Dew

  2. wekempfNo Gravatar says:

    I know this is meant for beginners, so I should not be so nit-picky, but you got a lot of details wrong. A deadlock does not mean “the application will die”. Quite the contrary, it’s going to continue to run. Any threads involved in the deadlock will block forever, but the application will continue running. We generally don’t use semaphores, but instead mutexes to ensure mutual exclusion. A semaphore is used for more complex scenarios. (http://www.barrgroup.com/Embedded-Systems/How-To/RTOS-Mutex-Semaphore) You’re description of what task is returned is hard to understand, and I’m not sure the details are all that important to the discussion.

    var run = Task.Factory.StartNew(() => {
    Task.Delay(2000);
    });

    That bit is dodgy. Task.Delay returns a Task. You’ve wrapped a Task inside a Task. That’s not always bad, but in this case it is for a couple of reasons. First, this isn’t doing what you think it’s doing. The Run task is going to complete immediately without delaying. Second, you’re not observing the inner task, which is bad form. It’s not as bad as it was in previous versions of .NET (the behavior of unobserved Tasks that throw has changed) and this particular Task shouldn’t throw, but this is still very bad code to show to newbies.

    Whether or not RunAsync().Wait() will deadlock is more nuanced then you present, though that’s picking nits since you’re talking about a specific code example. It still bothers me, though, because you’re giving an incomplete picture to the newbie. Not all async methods will cause such a deadlock. (See Task.ConfigureAwait, for one example of how a Task may not cause deadlock here.)

    In any event, calling Wait() on the UI thread isn’t something you should do, regardless. Even if you know it won’t deadlock the UI thread, blocking calls of any type should not be made on the UI thread. Instead, the code in question should have done “await RunAsync()”.

  3. Filip EkbergNo Gravatar says:

    Thanks for your input!

    You’re technically right of course, deadlock is defined as “a situation in which two or more competing actions are each waiting for the other to finish, and thus neither ever does.” according to Wikipedia. However if this occurs in your application as I’ve demonstrated, the application will hang/crash/die; or rather the OS will detect that it’s not responding and ask you to kill it. Too me this is the same as crash/death.

    I updated the article to use Thread.Sleep instead of Task.Delay to avoid any confusion. The result will still be the same; a deadlock.

    Thanks again, I think your comment is truly valuable to someone wanting to dig one step deeper!

  4. vtortolaNo Gravatar says:

    This is, by far, the best explanation I have found to the deadlock. And I have read some. Thanks!

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>