Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

async/await deadlock when using WindowsFormsSynchronizationContext in a console app

Tags:

c#

async-await

As a learning exercise, I'm trying to reproduce an async/await deadlock that occurs in a normal windows form, but using a console app. I was hoping the code below would cause this to happen, and indeed it does. But the deadlock also happens unexpectedly when using await.

using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
static class Program
{
    static async Task Main(string[] args)
    {
        // no deadlocks when this line is commented out (as expected)
        SynchronizationContext.SetSynchronizationContext(new WindowsFormsSynchronizationContext()); 
        Console.WriteLine("before");
        //DoAsync().Wait(); // deadlock expected...and occurs
        await DoAsync(); // deadlock not expected...but also occurs???
        Console.WriteLine("after");
    }
    static async Task DoAsync()
    {
        await Task.Delay(100);
    }
}

I'm mostly curious if anyone knows why this is happening?

like image 989
Darragh Avatar asked Nov 20 '19 17:11

Darragh


People also ask

How can we avoid deadlock in async await?

The solution is simple, use async all the way down. Never block on tasks yourself. Another solution is to call “ConfigureAwait(false)” on the task of the underlying method, to prevent the continuation of the task on the original context captured. If you really cannot use async all the way, then you could use “Task.

Does await block the thread?

Because await is only valid inside async functions and modules, which themselves are asynchronous and return promises, the await expression never blocks the main thread and only defers execution of code that actually depends on the result, i.e. anything after the await expression.

How does async relate to the current SynchronizationContext?

In other words, before the async method yields to asynchronously wait for the Task 't', we capture the current SynchronizationContext. When the Task being awaited completes, a continuation will run the remainder of the asynchronous method.

How does async await works in C#?

The async keyword turns a method into an async method, which allows you to use the await keyword in its body. When the await keyword is applied, it suspends the calling method and yields control back to its caller until the awaited task is complete. await can only be used inside an async method.


1 Answers

This happens because the WindowsFormsSynchronizationContext depends on the existence of a standard Windows message loop. A console application does not start such a loop, so the messages posted to the WindowsFormsSynchronizationContext are not processed, the task continuations are not invoked, and so the program hangs on the first await. You can confirm the non-existence of a message loop by querying the boolean property Application.MessageLoop.

Gets a value indicating whether a message loop exists on this thread.

To make the WindowsFormsSynchronizationContext functional you must start a message loop. It can be done like this:

static void Main(string[] args)
{
    EventHandler idleHandler = null;
    idleHandler = async (sender, e) =>
    {
        Application.Idle -= idleHandler;
        await MyMain(args);
        Application.ExitThread();
    };
    Application.Idle += idleHandler;
    Application.Run();
}

The MyMain method is your current Main method, renamed.


Update: Actually the Application.Run method installs automatically a WindowsFormsSynchronizationContext in the current thread, so you don't have to do it explicitly. If you want you can prevent this automatic installation, be configuring the property WindowsFormsSynchronizationContext.AutoInstall before calling Application.Run.

The AutoInstall property determines whether the WindowsFormsSynchronizationContext is installed when a control is created, or when a message loop is started.

like image 124
Theodor Zoulias Avatar answered Oct 17 '22 18:10

Theodor Zoulias