众所周知,在UI线程中访问Task的Result属性,同步模式将死锁。
按照理论,遵循代码将死锁,但不会死锁。你能解释为什么吗?
// My "library" method.
public static async Task<JObject> GetJsonAsync(Uri uri)
{
using (var client = new HttpClient())
{
var jsonString = await client.GetStringAsync(uri);
return JObject.Parse(jsonString);
}
}
//MVC action
public ActionResult Index()
{
var result = System.Threading.Tasks.Task.Run(async () => await GetJsonAsync(...)).Result; // deadlock is expectation but not :(
...
}
我认为System.Threading.Tasks.Task.Run(async () => await GetJsonAsync(...)).Result
在某种程度上类似于GetJsonAsync(...).Result
,但不是。
GetJsonAsync(...)).Result
将陷入僵局
但是System.Threading.Tasks.Task.Run(async () => await GetJsonAsync(...)).Result
不是。
答案 0 :(得分:3)
Result
本身不会导致死锁 。当从单线程上下文中调用时,它会导致死锁。 ,如果该任务还有一个await
,也需要该上下文。
await
默认情况下会捕获上下文并在该上下文上恢复。 (您可以使用ConfigureAwait(false)
覆盖此默认行为并改为在线程池线程上继续。)Result
阻止当前线程,直到Task
完成为止。 (您可以使用await
异步使用任务,以避免阻塞线程。)Task.Run
在具有线程池上下文(不是单线程上下文)的线程池线程上运行代码。)因此,要陷入僵局,您需要拥有一个await
来捕获单线程上下文,然后在该上下文中阻塞线程(例如,在该任务上调用Result
)。 await
需要上下文来完成Task
,但是上下文一次仅允许一个线程,并且Result
在该上下文中保持线程阻塞,直到{{1} }完成。
在您的示例中,您正在Task
内调用GetJsonAsync
,并在线程池上运行它。因此,Task.Run
中的await
(以及传递给GetJsonAsync
的委托中的await
)捕获线程池上下文,而不是ASP.NET请求线程上下文。然后您的代码调用Task.Run
,它确实阻塞了ASP.NET请求线程(及其上下文),但是由于Result
不需要该上下文,因此没有死锁