In the previous post , we saw some the basics of .NET deadlocks. Including explanation of locks, how to debug deadlocks with the Thread Window and how to fix a deadlock. Well, one specific type of deadlock.

Now that we got our bases covered, this part is going to be even more interesting. We’ll see two more deadlock types: The notorious Dispatcher-Queue Deadlock and the Sync-Context Deadlock (both names coined by me just now). In addition, I’ll show you a new debugging technique for deadlocks and multi-threaded scenarios.

The Dispatcher-Queue Deadlock

One of the most common ways we use Threads (well, asynchronous programming) is so as not to block the UI Thread. This is a key point in Desktop applications.

Any interaction with the UI, like a button-click, starts on the UI Thread (Event handler or Command). While you’re doing stuff on the UI thread, your Window will freeze entirely. We don’t want that.

If, however, that something is done on a different Thread, there’s no freeze and everything is fine. After the operation on a different Thread is finished, we might want to do UI changes. Any UI changes should be done on the UI Thread . This means we need to go back to the UI Thread. And this particular issue has caused more deadlocks than I can count. Here’s an example:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();
    }
 
    private void OnButtonClick(object sender, RoutedEventArgs e)
    {
        Task.Run(() =>
        {
            Console.WriteLine("Long operation on another thread");
            Dispatcher.Invoke(() => 
              // This will occur on the UI Thread
              MyTextBox.Text = "operation finished");
        }).Wait();
        Console.WriteLine("Do more stuff after the long operation is finished");
    }
}

Explanation:

  • The is a simple WPF application
  • OnButtonClick is an event-handler of a Button Click, that executes on the UI Thread
  • Task.Run() executes work on a ThreadPool Thread.
  • Dispatcher.Invoke() is a WPF method that synchronously executes work on the UI Thread. It queues work on the Dispatcher-Queue and waits for it to finish.
  • .Wait() waits for the task to finish, so it keeps the UI Thread busy. But, the task is waiting for the UI Thread to be freed in order to finish the Dispatcher.Invoke() call. Hence, we have a deadlock.

There are several ways we can solve the problem here. If you’re thinking about async/await , then you are in the right direction, and I’m getting there in a minute. But I want to show other alternatives as well, to better understand the scenario.

Solution #1 – Move the “stuff after” upwards

The [task].Wait() is intended to do additional stuff after the task. But why not just write that additional stuff inside the delegate? Like this:

private void OnButtonClick(object sender, RoutedEventArgs e)
{
	Task.Run(() =>
        {
        Console.WriteLine("Long operation on another thread");
        Dispatcher.Invoke(() =>
        {
            // This will occur on the UI Thread
            MyTextBox.Text = "operation finished";
        });
        Console.WriteLine("Do more stuff after the long operation is finished");
    });
}

There you go, no deadlock. If you would want that additional stuff to execute on the UI Thread, simply placing them within the Dispatcher.Invoke scope is enough.

Solution #2 – ContinueWith

Another way to do this is with the ContinueWith method:

private void OnButtonClick(object sender, RoutedEventArgs e)
{
    Task.Run(() =>
    {
        // do something
        Console.WriteLine("Operation on another thread");
        Dispatcher.Invoke(() => MyTextBox.Text = "operation finished");
        
    }).ContinueWith(t=>
    {
        Console.WriteLine("Do more stuff after the long operation is finished");
    },
    /*The following argument is optional. When omitted, the continuation will be executed on a ThreadPool Thread
    TaskScheduler.FromCurrentSynchronizationContext()*/);
}

This creates a nicer logical separation of the work units. The “Do more stuff” part is executed after the original task and you can control whether it will be executed on the UI Thread or not. In the example, TaskScheduler.FromCurrentSynchronizationContext() makes it execute on the UI Thread.

In fact, this is very similar to what async/await does behind the scenes. In its most simple form, await translates the code after the awaited call to this type of ContinueWith code.

Solution #3 – Asynchronous execution with BeginInvoke

As mentioned, Dispather.Invoke queues a body of work to the UI Thread’s message queue and waits for it to finish. But, we can use Dispather.BeginInvoke instead. It will queue work on the UI Thread, but will not wait for it to finish. Here’s how it look in code:

private void OnButtonClick(object sender, RoutedEventArgs e)
{
    Task.Run(() =>
    {
        // do something
        Console.WriteLine("Operation on another thread");
        Dispatcher.BeginInvoke((Action) (() => MyTextBox.Text = "operation finished"));
    }).Wait();
    // This will wait for the long operation, but not for the UI Thread stuff
    Console.WriteLine("Do more stuff after the long operation is finished");
}

The difference though is that whatever happens within Dispatcher.BeginInvoke is not waited for. It queues the delegate to the Dispatcher-Queue and continues on.

Solution #4 – Use async/await

Since C# 5 (and .NET 4.5), we got a powerful tool in our hands – The async/await asynchronous programming paradigm. The idea is that you can write asynchronous code in a synchoronous manner. That is, without callback functions, ContinueWith and the like. Here is a solution using async/await:

private async void OnButtonClick(object sender, RoutedEventArgs e)
{
    await Task.Run(() =>
    {
        // do something
        Console.WriteLine("Operation on another thread");
    });//You can use .ConfigureAwait(false) to not-force execution on the UI Thread and possibly prevent deadlocks - more on this later
    MyTextBox.Text = "operation finished";
    Console.WriteLine("Do more stuff after the long operation is finished");
}

This code is much nicer than the previous solutions. The code is asynchronous but is written like simple synchronous calls – beautiful. Using async/await is not always possible though. For example, you might be using a lower C# version. Or your context isn’t an async method.

Noticed the little comment about await XXX.ConfigureAwait(bool) ? It has big implications regarding deadlocks as well, and we’ll revisit that really soon.

All the examples are shown with WPF, but WinForms has the same problems and solutions. Instead of Dispatcher.[begin]Invoke, you can call .Invoke and .BeginInvoke on any UI Control. The async/await also works the same by returning the execution after await to run on the UI Thread. This is done by utilizing the SynchronizationContext, which we’ll talk about later.

Best Practices for using the UI Thread and Dispatcher

  • Remember not to do long operations on the UI Thread so as not to freeze it
  • When going back to the UI Thread, you can potentially cause deadlocks. Be especially careful when using synchronous operations like Dispatcher.Invoke and task.Wait()
  • Use async/await if possible. It makes the code more readable and you utilize .ConfigureAwait(false) to prevent deadlocks (more on this later)

Debugging with the Tasks Window

Before I show you another deadlock, let’s talk a little about debugging. Did you ever use the Tasks Window ? It’s pretty neat actually. It shows all the currently running Tasks. This includes their Status, Start Time, Duration, Location in code and so on. And one of the cool things about it is that it automatically detects deadlocks. Here’s how it looks when debugging the deadlock example from part 1 :

The caveat with the **Tasks Window** is that it only works on **Tasks**. That’s why you won’t see the UI Thread in there, or any thread created with the Thread class or the ThreadPool class. Still, can be useful. To sense the full power of it, here’s a nice demo with a complex multi-threaded scenario:

The Sync-Context Deadlock

The async/await paradigm seems great on first sight. But, like almost everything in programming, it needs to be thoroughly understood to be properly used. One potential danger manifests when you are using an async method from a non-async method. In those cases, you can use the .Result property to call the async method, wait for it to finish, and get the result. Consider this for example:

private void OnButtonClick(object sender, RoutedEventArgs e)
{
    var x = Do().Result;
}

private async Task<int> Do()
{
    await Task.Delay(1000);
    return 5;
}</int>

Seemingly an innocent piece of code… Or is it? When executed, this actually causes a deadlock. Can you see why? To understand it, we’ll have to dive a little deeper into async/await internals.

The important question is: On which Thread will the rest of the method (after await) be executed? Let’s talk a bit about SynchronizationContext. async/await was designed to allow you to await a method asynchronously and then have the rest of the code executed in the same Context. In other words, if called from the UI Thread, the code after await should also be in the UI Thread. Otherwise, this wouldn’t work:

private async void OnButtonClick(object sender, RoutedEventArgs e)
{
    await SomeAsyncMethod();
    MyTextBox.Text = "operation finished";// Must be on UI Thread
}

Consider that SomeAsyncMethod is executed on a ThreadPool Thread so as not to freeze the UI. The async/await mechanism uses the current Thread’s SynchronizationContext to know where to execute the rest of the method. When called from the UI Thread, its SynchronizationContext will add to the Dispatcher Queue. Other Threads don’t have a SynchronizationContext, in which case there’s a Default SynchronizationContext. This means the rest of the method will just keep executing on the same Thread or on a ThreadPool Thread (depends on some stuff, like if there was an I/O bound operation awaited).

The explanations in this article might over-simplify the concepts. The SynchronizationContext, the TaskScheduler, and the technology behind async/await is complicated with many edge-cases. For a fuller (and longer) explanation here are some great articles: - Parallel Computing – It’s All About the SynchronizationContext

You can configure whether to try use a captured SynchronizationContext or not by using .ConfigureAwait(). For example: await Task.Delay(1000).ConfigureAwait(false) – means to use the Default SynchronizationContext, which will usually continue on a ThreadPool Thread, even if called from UI Thread . To summarize, the rules are:

  • By default, if we were running on the UI Thread before await, then we will keep running on the UI Thread after await.
  • If we were running on a non-UI Thread before await, then we will either keep running on the same Thread or on a ThreadPool Thread. But it will not be the UI Thread. This is safe and will not cause deadlocks.
  • We can use await SomeAsyncMethod.ConfigureAwait(false) to have the rest of the method keep execution on the same Thread as the Async method. Which will usually be a ThreadPool Thread. This is safe and will not cause deadlocks.

Now let’s get back to our deadlock.
The event handler is executed on the UI Thread. Then, we call Do method, which is calling await Task.Delay(1000). When the Delay is finished, it uses the current SynchronizationContext (which is the captured one from the UI Thread) which uses the Dispatcher Queue to try run it on the UI Thread. However, the UI Thread is stuck, because we used the synchronous .Result property. It’s waiting for Do to finish – causing a deadlock.

Solution #1 – Set ConfigureAwait to False

Consider the following change:

private void OnButtonClick(object sender, RoutedEventArgs e)
{
    var x = Do().Result;
    //do more stuff
}

private async Task<int> Do()
{
    await Task.Delay(1000).ConfigureAwait(false);
    return 5;
}</int>

The addition of .ConfigureAwait(false) doesn’t use the current Thread’s (captured) SynchronizationContext. Which means the rest will run on a ThreadPool Thread and resolve the deadlock.

This solution is great… when possible. What if I wanted to make UI changes within the Do method? Or it might be some 3rd party code that didn’t bother to use .ConfigureAwait(false) . In that case, we got other options:

Solution #2 – Change the calling method to Async

This solution should’ve been mentioned first, but I wanted you to understand ConfigureAwait and
SynchronizationContext while it’s still fresh.

When using async methods all the way in your call stack, you won’t have these problems:

private async void OnButtonClick(object sender, RoutedEventArgs e)
{
    var x = await Do();
    //do more stuff
}

private async Task<int> Do()
{
    await Task.Delay(1000);
    return 5;
}</int>

So why does this work and the other way causes a deadlock?

When writing var x = Do().Result the UI Thread is executing this method and is busy. However, when writing var x = await Do() , the UI Thread is freed and after the awaited method is finished, we are adding the “do more stuff” part to the UI Message Queue.

Solution #3 – Run from a non-UI Thread

Sometimes, changing your entire call stack to be async method just isn’t possible. Here’s another alternative:

private void OnButtonClick(object sender, RoutedEventArgs e)
{
    int x;
    Task.Run(()=> x = Do().Result)
        .Wait();
    //do more stuff
}

private async Task<int> Do()
{
    await Task.Delay(1000);
    return 5;
}

This works because await Task.Delay(1000) is called from a ThreadPool Thread. It doesn’t have SynchronizationContext and the rest of the method is also continued on a ThreadPool Thread.

Best Practices for async/await

  • In library code, use .ConfigureAwait(false) if possible. You don’t know which Thread called you and what deadlocks you might cause.
  • Having your entire call stack as async methods makes more readable code and it’s easier to avoid deadlocks.
  • Be careful about using .Result or .GetAwaiter().GetResult() . They can easily cause deadlocks, especially when called from the UI Thread.

Summary

I hope you now understand a bit more about deadlocks, the Dispatcher Queue, SynchronizationContext and the mechanics of async/await. It’s one of those things that you can use successfully for years without really understanding how it works under the hood. Once you understand the internals, the deadlocks will transform from scary bugs to easy challenges.

I recommend reading the articles I mentioned before [1] [2] [3] – They are quality pieces and explain in detail the internals of async/await, SynchronizationContext and all those other concepts.

I’m not done with you yet. In the next part of the series, I’ll show you some advanced debugging techniques so stay tuned and subscribe to the blog to the get notified about the new article.