我需要解释为什么这段代码不会在运行它的线程上导致死锁(这是一个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/了解更多信息。