为什么这个异步方法不会导致线程死锁?
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/。
我需要一个解释,为什么这段代码不会导致 运行 所在的线程死锁 (这是一个 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/。