marko devcic

  • github:
    deva666
  • email:
    madevcic {at} gmail.com

Async methods and deadlocks

Posted on 21 May 2014

I jumped on the new .NET 4.5 async/await wagon without getting familiar with what's under the hood and learned the hard way how to properly call async methods.

Here's a simplified example of what I was doing (I was actually waiting on a void returning Task but this example server the purpose better) ...

        private void button1_Click(object sender, EventArgs e)
        {
            var result = GetWebAsync().Result; //Deadlock
        }

        private async Task<string> GetWebAsync()
        {
            HttpClient client = new HttpClient();
            string result = await client.GetStringAsync("http://www.google.com");
            return result;
        }

And call to button1_Click event handler will cause a deadlock.

Let's look better what's going on, a call to Button_Click event handler calls GetWebAsync that returns a Task object. A task that promises to have a string as a result some time in the future.
The Task has not completed at this moment.
Immediately after the Task has been returned, variable result needs the value from the Task (by calling .Result property), but the value is not yet available and the calling thread needs to block until the Task completes and returns a value.

Meanwhile, inside GetWebAsync method HttpClient is constructed and asynchrounous call GetStringAsync is made.
GetStringAsync is awaited, ie. as soon as the call has been made, the GetWebAsync method was suspended at the awaiter and it returned control to the caller.
When the awaited async method completes and produces a string the GetWebAsync method resumes at the suspended point at previously captured context.
The same context that is blocked. And there it is, a deadlock.

Two important things need to be clear, first of all waiting on a Task (via Result or Wait() method) causes the calling thread to block until it receives a notification that Task has completed.
Second, awaitable continues (if not told otherwise) on a captured synchronization context.

With that in mind, there's two ways how this deadlock be avoided.

First, the better way, make all the methods async.
Await is not a blocking call. Also async methods can return Task, Task and be void.
So it's possible to make your event handlers asynchrounos.

        private async void button1_Click(object sender, EventArgs e)
        {
            var result = await GetWebAsync();
        }

Second, any awaitable can be configured to not continue on the captured synchronization context with ConfigureAwait(false) call.

        private void button1_Click(object sender, EventArgs e)
        {
            var result = GetWebAsync().Result; 
        }

        private async Task<string> GetWebAsync()
        {
            HttpClient client = new HttpClient();
            string result = await client.GetStringAsync("http://www.google.com").ConfigureAwait(false);
            return result;
        }

Now this was specific to new .NET 4.5 async/await features, but this kind of bug can also be easily induced in .NET 4.0.

Here's the same problem, spin off a some blocking code on a thread pool thread, schedule a continuation delegate on a UI thread and then block the UI thread with Task.Wait().

        private void DeadLock()
        {
            var task = Task.Factory.StartNew(() => Thread.Sleep(3000));
            var continueTask = task.ContinueWith(t => Console.WriteLine("You wont see this text"), TaskScheduler.FromCurrentSynchronizationContext());
            continueTask.Wait(); //Deadlock;            
        }

So same principle applies here, don't block with Task.Wait() if the continuation delegate is scheduled on the captured context or let it spin off also on a thread pool thread.