众所周知,在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 :(
...
}
Run Code Online (Sandbox Code Playgroud)
我认为这在某种程度上System.Threading.Tasks.Task.Run(async () => await GetJsonAsync(...)).Result是相似的,但事实并非如此。GetJsonAsync(...).Result
GetJsonAsync(...)).Result会陷入僵局,但System.Threading.Tasks.Task.Run(async () => await GetJsonAsync(...)).Result事实并非如此。
Result本身不会导致死锁。当从单线程上下文调用时,如果该await任务也需要该上下文,则会导致死锁。
更多细节:
await默认情况下捕获上下文并在该上下文上恢复。(您可以用来ConfigureAwait(false)覆盖此默认行为并在线程池线程上恢复。)Result阻塞当前线程直到Task完成。(您可以使用await异步方式使用任务以避免阻塞线程。)Task.Run线程池上下文在线程池线程上运行代码,该线程池上下文不是单线程上下文。)因此,要获得死锁,您需要捕获await单线程上下文,然后阻止该上下文内的线程(例如,调用Result该任务)。需要await上下文来完成Task,但上下文一次只允许一个线程,并且 会Result在该上下文中保持线程阻塞,直到完成Task。
在您的示例中,您在GetJsonAsynca 内部调用Task.Run,它在线程池上运行它。因此awaitin GetJsonAsync(以及await传递给 的委托中Task.Run)捕获线程池上下文,而不是 ASP.NET 请求线程上下文。然后您的代码调用Result,这确实会阻止 ASP.NET 请求线程(及其上下文),但由于await不需要该上下文,因此不会出现死锁。