Deadlocks when using Tasks – solved

In my last post Deadlocks when using Tasks I explained how this innocent looking piece of code can cause a GUI app to deadlock and die. I proceeded to explain the reasons behind this behaviour. In this post I aim to discuss one possible solution to this problem and highlight some potential design considerations that you should be aware of while working with TAP code that uses asynchronous calls mixed with synchronous wait calls. For convenience this is the piece of code that would deadlock your application

public static class NiksMessedUpCode
    {
        private static async Task DelayAsync()
        {
            await Task.Delay(1000);
        }
 
        public static void Test()
        {
            // Start the delay.
            var delayTask = DelayAsync();
            // Wait for the delay to complete.
            delayTask.Wait();
        }
    }

 

Solution

Modifying the code as shown below will remove the deadlock. Can you spot what we did here?

public static class NiksMessedUpCode
{
    private static async Task DelayAsync()
    {
        await Task.Delay(1000).ConfigureAwait(false);
    }
 
 
    public static void Test()
    {
        // Start the delay.
        var delayTask = DelayAsync();
        // Wait for the delay to complete.
        delayTask.Wait();
    }
}

 

Yes we added a ConfigureAwait(false) to the Delay method call on our Task. Go on try this code in the GUI app. This should not deadlock anymore.

Explanation

As you are aware when a Task execution is started .NET runtime effectively captures the existing context under which the application was currently running and immediately returns. Once the awaited Task has finished its execution the .NET runtime would execute the remainder of the async method on the same context that it captured before the Task was awaited. In a VERY simplistic manner you can imagine something like this (borrowed from Stephen Toub’s article on MSDN here). Logically you can think of the following code:

await FooAsync();
RestOfMethod();

as being similar in nature to this:

var t = FooAsync();
var currentContext = SynchronizationContext.Current;
t.ContinueWith(delegate
{
    if (currentContext == null)
        RestOfMethod();
    else
        currentContext.Post(delegate { RestOfMethod(); }, null);
}, TaskScheduler.Current);

This logical execution however changes when ConfigureAwait is added to the mix.

Using ConfigureAwait(false) actually instructs the .NET runtime to not bother with capturing the context before the await statement and hence execute the remainder of the method execution post await statement on any thread pool context.

As I mentioned in my previous article the cause of the deadlock was that there is no way for the await call in the DelayAsync method to signal its completion as the delayTask.Wait() statement is blocking up the SynchronizationContext from running any other chunk of code. This then becomes the cause of the deadlock. You can read the whole explanation here

Using ConfigureAwait however enables the await to execute the remainder of the code in DelayAsync method on a thread pool context and thus gets us around the deadlock. The main SynchronizationContext which was waiting on the delayTask.Wait() statement gets to know about the completion of the await call and hence can execute the rest of the code.

Caution with ConfigureAwait

The fact that using ConfigureAwait signals the await mechanism to run the remainder of the method on any thread pool context and not necessarily on the original context which was used to run the code before the await system has an important implication.

Given that using ConfigureAwait causes an effective loss of the original context you should not use ConfigureAwait within any code block that directly manipulates GUI elements.

For instance following is an example where you cannot use ConfigureAwait

private async void Button_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                button1.IsEnabled = false;
                //CANT USE CONFIGUREAWAIT HERE!!!!
                await SomeTask();
                
            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
                //do somethingwith the exception here
            }
            finally
            {
               /// When the await is over this line of code will have to be run on the original thread that created the button element
               /// thus we cannot use configureawait in the preceeding await.
                
                button1.IsEnabled = true;
            }
        }

        private async Task SomeTask()
        {
            await Task.Delay(3000);
            
        }

If you were to try and use ConfigureAwait in the button click handler you will see an exception being raised. Go on try using the above code.

You can however use ConfigureAwait in the SomeTask method as shown below and in fact I would recommend you do this

      private async void Button_Click(object sender, RoutedEventArgs e)
        {
            try
            {
                button1.IsEnabled = false;
                //CANT USE CONFIGUREAWAIT HERE!!!!
                await SomeTask();

            }
            catch (Exception ex)
            {
                MessageBox.Show(ex.Message);
                //do somethingwith the exception here
            }
            finally
            {
                /// When the await is over this line of code will have to be run on the original thread that created the button element
                /// thus we cannot use configureawait in the preceeding await.

                button1.IsEnabled = true;
            }
        }

        private async Task SomeTask()
        {
            // Yup can use ConfigureAWait here even though the button handler calls this. Why? Read my blog!!
            await Task.Delay(3000).ConfigureAwait(false);

        }

Remember each async method has its own context! Thus when we call into the method SomeTask it starts with its own context which is different from the main SynchronizationContext that was running the button click handler. It is then perfectly valid to use ConfigureAwait in this method as when this code runs (and await returns) the .NET runtime would take care to marshal back the remainder of the button click handler on the original SynchronizationContext (and hence the correct thread) completely independently of where it ran the rest of the SomeTask method post its await statement.

A natural deduction of the above discussion is the recommendation that you use as minimum code as possible within the actual button click event handler and write the bulk of the code in other async method which you call from the actual click handler. This would free up the main GUI thread of your app to only handle the important GUI related messages while running the other async methods on different contexts thus bringing in an element of parallelism in your code and improving performance further.

Summary

As a conclusion I would recommend creating an effective barrier in your code between code that runs on or manipulates GUI elements and the rest of the code and have the rest of the GUI free code embrace COnfigureAwait to free up main GUI thread even further. For ASP.NET applications the context sensitive code would be any method block that works with HttpContext.Current, builds up HttpResponse or returns from controller methods.

Final thoughts and a small teaser!

Actually thinking more on this there are further subtle nuances on how the await mechanism captures contexts. You might be surprised if I state that there are legit cases when .NET runtime would not capture context at all before an await statement even if you do not use ConfigureAwait within that await statement block. That however is a topic for another post if there is an appetite for that. Let me know!

 

Leave a Comment

Your email address will not be published.

2 Trackbacks

  1. Fixing Patterns for Async MVVM Applications | Nik's Corner (Pingback)
  2. Gregory Smith (Trackback)