首页 > 解决方案 > 在控制台应用程序中使用 WindowsFormsSynchronizationContext 时出现异步/等待死锁

问题描述

作为一项学习练习,我正在尝试重现在普通 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);
    }
}

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

标签: c#async-await

解决方案


WindowsFormsSynchronizationContext会将其给定的任何委托发布到由 UI 线程提供服务的 WinForms 消息循环。但是,您从未设置其中之一,也没有 UI 线程,因此您发布的任何内容都会消失。

所以你await正在捕获一个SynchronizationContext永远不会运行任何完成的东西。

正在发生的事情是:

  1. Task正在从Task.Delay
  2. Task主线程使用自旋锁(in Task.SpinThenBlockingWait)同步启动等待此操作完成
  3. 自旋锁超时,主线程创建一个等待事件,由Task上的一个continuation设置
  4. Task 完成(你可以看到它已经完成,因为它的 Status 是 RanToCompletion)
  5. 任务尝试完成将释放主线程正在等待的事件的延续(Task.FinishContinuations)。这最终会调用TaskContinuation.RunCallback(尽管我还没有追踪到那个调用路径),它调用了你的WindowsFormSynchronizationContext.Post.
  6. 但是,Post什么都不做,就会发生死锁。

为了获得这些信息,我做了以下事情:

  1. 尝试调用new WindowsFormsSynchronizationContext.Post(d => ..., null),看看委托没有被调用。
  2. 构建我自己的SynchronizationContext并安装它,看看什么时候Post被调用。
  3. 在死锁期间打破调试器,查看Threads并查看Call Stack主线程。
  4. 在变量中捕获正在等待的任务,在监视窗口中查看它,右键单击 -> 生成对象 ID,然后将该对象 ID 放入监视窗口中。让它死锁、中断,并根据其对象 ID 在监视窗口中检查任务。

推荐阅读