为什么这个异步方法不会导致线程死锁?

Why this async method don't cause deadlock on a thread?

我需要一个解释,为什么这段代码不会导致 运行 所在的线程死锁 (这是一个 WinForm 应用程序,这些发生在 button_Click):

Task FakeMainThread = Task.Run(async() =>  // fake main thread
{
    async Task asyncMethod()               // executed in FakeMainThread
    {
        await Task.Run(() =>               // 'await'ing in FakeMainThread
        {
            Thread.Sleep(5000);
            MessageBox.Show("child Task finished");
            return 3;
        });
        MessageBox.Show("asyncMethod finished");
    }

    asyncMethod().Wait();     // deadlock should appear in FakeMainThread 
    MessageBox.Show("FakeMainThread finished");
    return 4;
});

asyncMethod().Wait(); 应该在等待另一个任务完成时阻塞 fakeMainThread 任务的线程,并且由于 await 也在 fakeMainThread 中发生,它不应该能够 'await' 并且死锁应该出现在 fakeMainThread 上。但它并没有发生,我看到机器人 MessageBox.Show("asyncMethod finished");MessageBox.Show("FakeMainThread finished"); 消息。 注意:如果我把这个:

async Task asyncMethod()               
{
    await Task.Run(() =>              
    {
        Thread.Sleep(5000);
        MessageBox.Show("child Task finished");
        return 3;
    });

    MessageBox.Show("asyncMethod finished");
}

asyncMethod().Wait();          // deadlock appears in Main Thread (UI)
MessageBox.Show("FakeMainThread finished");

in main button1_click() inside directli deadlock appears on UI.谢谢!

由于 SynchronizationContext,您在第一个代码示例中没有遇到死锁。这个上下文说明了 await 必须做什么来恢复你的代码。当您开始一个新任务时 (Task.Run),您将获得默认上下文。在 button1_click 中,您从表单中获取上下文。

表单的上下文只允许执行一个线程(用于 paint/update 您的表单的线程)。

当您调用 .Wait() 时,您会保持该线程 'locked' 等待任务完成。这意味着该线程不会为另一项工作而释放,这是表单进入 'not responding' 状态的原因之一。

当您执行 await [code] 并且该代码已完成时,它将要求同步上下文安排代码的其余部分。

  • 在默认上下文的情况下,任何空闲线程都会被占用,您的代码将继续,任务被标记为已完成,因此 .Wait() 中的 'loop' 会收到任务信号完成并继续。
  • 在表单上下文的情况下,只有一个线程,因此此任务的完成安排在当前代码完成后 运行。这就是死锁的原因。

避免这种死锁是您最常收到使用 ConfigureAwait(false) 建议的主要原因,这告诉 await 它应该用默认同步上下文替换当前同步上下文,从而允许你可以使用任何空闲线程。 您不想使用它(或者说 ConfigureAwait(true))的唯一原因是,如果您需要更新表单(WPF 或 WinForms)上的某些内容,或者您​​需要 http 上下文(ASP.NET,而不是 ASP.NET核心)。这是因为只有一个线程可以访问您的表单或 http 上下文。在这里,您的 MessageBox.Show 不会出现异常,因为这是表单上下文的 'outside',不需要这种特殊的 thread/synchronization 上下文。

有关详细信息,请参阅 https://devblogs.microsoft.com/dotnet/configureawait-faq/