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



我需要解释为什么这段代码不会在运行它的线程上导致死锁(这是一个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中,所以它不应该"等待",并且死锁应该出现在fakeMain线程上。但这并没有发生,我看到了机器人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");

在directli内部的主按钮1_click((中,UI上出现死锁。谢谢

由于SynchronizationContext,您在第一个代码示例中没有遇到死锁。这个上下文说明了等待必须执行什么操作才能恢复代码。当您启动一个新任务(Task.Run(时,您将获得默认上下文。在button1_click中,您可以从表单获得上下文。

表单的上下文只允许执行一个线程(用于绘制/更新表单的线程(。

当您调用.Wait()时,您将保持线程"锁定",等待任务完成。这意味着线程没有为另一个作业释放,这也是表单进入"未响应"状态的原因之一。

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

  • 在默认上下文的情况下,任何空闲线程都会被占用,代码将继续,任务被标记为已完成,因此.Wait()中的"循环"会得到信号,表示任务已完成并继续
  • 在表单上下文的情况下,只有一个线程,因此该任务的完成计划在当前代码完成后运行。这就是造成僵局的原因

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

请参阅https://devblogs.microsoft.com/dotnet/configureawait-faq/了解更多信息。

最新更新