在控制台应用程序中使用WindowsFormsSynchronizationContext时,异步/等待死锁

Dar*_*agh 6 c# async-await

作为一项学习练习,我试图重现以常规Windows形式但使用控制台应用程序发生的异步/等待死锁。我希望下面的代码会导致这种情况的发生,的确如此。但是使用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);
    }
}
Run Code Online (Sandbox Code Playgroud)

我最好奇是否有人知道为什么会这样?

The*_*ias 3

发生这种情况是因为这WindowsFormsSynchronizationContext取决于标准 Windows 消息循环的存在。控制台应用程序不会启动这样的循环,因此WindowsFormsSynchronizationContext不会处理发布到 的消息,不会调用任务继续,因此程序挂在第一个await. 您可以通过查询 boolean 属性来确认消息循环不存在Application.MessageLoop

获取一个值,该值指示该线程上是否存在消息循环。

要使其WindowsFormsSynchronizationContext正常工作,您必须启动消息循环。可以这样做:

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();
}
Run Code Online (Sandbox Code Playgroud)

MyMain方法是您当前的Main方法,已重命名。


更新:实际上该Application.Run方法会自动WindowsFormsSynchronizationContext在当前线程中安装 a ,因此您不必显式执行此操作。如果您希望阻止此自动安装,请WindowsFormsSynchronizationContext.AutoInstall在调用之前配置该属性Application.Run

AutoInstall属性确定是否WindowsFormsSynchronizationContext在创建控件时或启动消息循环时安装。