众所周知,在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 :(
...
}
我认为qazxsw poi在某种程度上类似于qazxsw poi,但不是。
System.Threading.Tasks.Task.Run(async () => await GetJsonAsync(...)).Result
将陷入僵局,但GetJsonAsync(...).Result
不会。
GetJsonAsync(...)).Result
本身不会造成僵局。当从单线程上下文调用时,如果该任务的System.Threading.Tasks.Task.Run(async () => await GetJsonAsync(...)).Result
也需要该上下文,则会导致死锁。
Result
:
await
默认捕获上下文并在该上下文中恢复。 (您可以使用More details覆盖此默认行为,并在线程池线程上继续。)await
阻止当前线程,直到ConfigureAwait(false)
完成。 (您可以使用Result
异步使用任务以避免阻塞线程。)Task
在线程池线程上运行代码,使用线程池上下文,这不是单线程上下文。)因此,要获得死锁,您需要有一个捕获单线程上下文的await
,然后在该上下文中阻塞一个线程(例如,在该任务上调用Task.Run
)。 await
需要上下文来完成Result
,但上下文一次只允许一个线程,并且await
在该上下文中保持线程被阻塞,直到Task
完成。
在你的例子中,你在Result
中调用Task
,它在线程池上运行它。所以GetJsonAsync
中的Task.Run
(以及传递给await
的委托中的GetJsonAsync
)捕获线程池上下文,而不是ASP.NET请求线程上下文。然后你的代码调用await
,它会阻塞ASP.NET请求线程(及其上下文),但由于Task.Run
不需要该上下文,因此没有死锁。