TLDR:在我下面的示例 (ASP.NET) 中,为什么 task1.Result 和 task2.Result 会导致死锁而 task3.Result 不会?
我们的代码中有一个类,我不能轻易地将方法标记为异步并防止代码在等待异步任务完成时阻塞。原因是我们的框架不支持。因此,我试图通过 task.Result 找到解决方案,但遇到了一些死锁。幸运的是我找到了解决方案(参见任务 3)。现在我试图找出 task1、task2 和 task3 之间的区别,以了解为什么前两个会导致死锁而第三个不会。
使用调试器我没有看到任何区别,比如在调用 task3.Result 之前运行 task3。它和其他两个一样仍然处于 WaitingForActivation 状态。任何人都可以向我解释一下 task3 是如何工作的吗?
public class HomeController : Controller
{
public ActionResult GetSomething()
{
var task1 = GetSomethingAsync();
var task2 = Task.Run(async () => await task1);
var task3 = Task.Run(async () => await GetSomethingAsync());
return Content(task1.Result);
// return Content(task2.Result);
// return Content(task3.Result);
}
private static async Task<string> GetSomethingAsync()
{
return await Task.Run(() => "something");
}
}
请您参考如下方法:
问题的根源是这里的await:
private static async Task<string> GetSomethingAsync()
{
return await Task.Run(() => "something");
}
上下文在进入等待之前被捕获,上下文是主线程。所以在等待上下文之后必须恢复,所以延续被安排在主线程中运行。由于此时主线程被阻塞,它无法处理继续,因此出现死锁。
要防止捕获上下文,您可以使用 ConfigureAwait(false) 配置此 await .
更新:continuation 我指的是 GetSomethingAsync 中等待之后的代码。尽管在那之后没有代码,但编译器似乎确实费心为方法的这个无操作部分创建一个实际的延续(否则在你的例子中不应该发生死锁)。
需要注意的是,编译器会将一个async方法转换为一个由多个小任务组成的Task。在执行路径中遇到的每个 await 都会导致创建一个迷你任务,该任务是等待任务的延续。所有这些小任务一个接一个地完成,最后一个任务的完成标志着 async 方法返回的“主任务”的完成。
