【发布时间】:2021-10-06 02:18:13
【问题描述】:
我正在阅读这个article 并找到了这个例子:
public static class DeadlockDemo
{
private static async Task DelayAsync()
{
await Task.Delay(1000);
}
// This method causes a deadlock when called in a GUI or ASP.NET context.
public static void Test()
{
// Start the delay.
var delayTask = DelayAsync();
// Wait for the delay to complete.
delayTask.Wait();
}
}
这种死锁的根本原因是等待处理上下文的方式。默认情况下,当等待一个不完整的任务时,当前的“上下文”被捕获并用于在任务完成时恢复该方法。这个“上下文”是当前的 SynchronizationContext,除非它是 null,在这种情况下它是当前的 TaskScheduler。 GUI 和 ASP.NET 应用程序有一个 SynchronizationContext,它一次只允许运行一段代码。当 await 完成时,它会尝试在捕获的上下文中执行 async 方法的其余部分。但是该上下文中已经有一个线程,它(同步地)等待异步方法完成。他们都在等待对方,造成了僵局。
我知道如果在await Task.Delay(1000); 之后有代码,它将成为延续的一部分,并且延续将在与Test() 相同的上下文中运行,这就是死锁的发生方式。
但是没有延续,那么死锁到底是怎么发生的呢?
或者,是否创建了一个空的延续?这样做有什么意义?
这是让我困惑的部分:
当等待完成时,它会尝试执行剩余的 捕获的上下文中的异步方法。
什么是“异步方法的剩余部分”?
【问题讨论】:
-
挑剔:如果等待 Task.Delay(1000) 之后有代码;该代码将在 DelayAsync() 的上下文中运行,这恰好与 Task() 相同,因为该方法是同步调用的。
-
大概,“异步方法的剩余部分”可能是一个什么都不做的空方法,但仍然需要调用。但我不知道是否记录了这种特定行为。
-
@DavidKlempfner
await说你想回到原来的同步上下文。它的行为不受其后是否有任何代码的影响。如果您想避免这种情况,请使用ConfigureAwait(false)或直接返回任务 -
@DavidKlempfner 好问题!
-
如果你等待的东西抛出了异常怎么办?
DelayAsync有两个步骤 - 第一步它执行Task.Delay,第二步在Task.Delay完成时执行,例如检查是否有异常。这第二步也是“继续”。
标签: c# asynchronous async-await task synchronizationcontext