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?
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.
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.
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.
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.
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 theWindowsFormsSynchronizationContext
is installed when a control is created, or when a message loop is started.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With