TLDR:在下面的示例(ASP.NET)中,为什么task1.Result和task2.Result导致死锁,而task3.Result没有死锁?
我们的代码中有一个类,在这里我无法轻松地将该方法标记为异步,并且在等待异步任务完成时不能阻止代码阻塞。原因是我们的框架不支持这一点。因此,我试图找到带有task.Result的解决方案并遇到一些僵局。幸运的是,我找到了一个解决方案(请参阅task3)。现在,我试图找出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");
}
}
答案 0 :(得分:2)
问题的根源是这里的await
private static async Task<string> GetSomethingAsync()
{
return await Task.Run(() => "something");
}
上下文在进入等待之前被捕获,并且上下文是主线程。因此,在等待之后必须恢复上下文,以便将继续安排在主线程中运行。由于此时主线程已被阻塞,因此它无法处理继续操作,从而导致死锁。
要防止捕获上下文,您可以将此await
与ConfigureAwait(false)
进行配置。
更新:通过 continuation 表示等待之后出现在GetSomethingAsync
中的代码。尽管此后没有代码,但似乎编译器会费心为该方法的此no-op部分创建实际的延续(否则在您的示例中不应出现死锁)。
应注意,编译器将async
方法转换为包含多个小型任务的Task
方法。执行路径中遇到的每个await
都会导致创建一个微型任务,该微型任务是等待任务的延续。所有这些微型任务都一个接一个地完成,最后一个微型任务的完成标志着async
方法返回的“主任务”的完成。
答案 1 :(得分:1)
GetSomethingAsync()
所做的一切由调用线程完成,直到某些操作(OP)无法立即完成(例如io),然后将控制流提供给调用函数。
如果然后访问返回的Result
对象上的Task
属性,线程将被阻塞。
这导致了一个问题,即使OP已经完成,线程也不会知道它,因为他正忙于等待Task
但是,如果让GetSomethingAsync()
被某个线程池线程(Task.Run(...)
这样做)执行,则线程池线程可以完成OP,并且可以通知调用线程{{1 }}已完成。
您的第二个方法不起作用,因为该任务仍在您的主线程上启动。如果你有这种方法
Task
并在带有public static async Task DoStuffAsync()
{
Console.WriteLine($"Doing some stuff1 on thread {Thread.CurrentThread.ManagedThreadId}");
await Task.Delay(50);
Console.WriteLine($"Doing some stuff2 on thread {Thread.CurrentThread.ManagedThreadId}");
}
SynchronizationContext
它将输出如下内容:
var task = DoStuffAsync();
Console.WriteLine($"Doing main stuff on thread {Thread.CurrentThread.ManagedThreadId}");
await Task.Run(async () => await task);
因此,通过代码行Doing some stuff1 on thread 1
Doing main stuff on thread 1
Doing some stuff2 on thread 1
,您仅实现了一个线程池线程等待原始Task.Run(async () => await task)
的完成,但这反过来又创建了一个新的Task
,如果没有,等待操作会导致死锁。