首页 > 解决方案 > Why access Task.Result in the synchronous mode doesn't cause a deadlock?

问题描述

As we all know to access the Result property of Task in the UI thread and synchronous mode will deadlock.

As theoretical follow code will deadlock but not. Can you please explain why?

// 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 :(

    ...
}

I thought that System.Threading.Tasks.Task.Run(async () => await GetJsonAsync(...)).Result is simialar to GetJsonAsync(...).Result in some way, but not.

GetJsonAsync(...)).Result will deadlock but System.Threading.Tasks.Task.Run(async () => await GetJsonAsync(...)).Result is not.

标签: c#async-awaitdeadlock

解决方案


Result本身不会导致死锁。如果该任务也await需要该上下文,则从单线程上下文调用时会导致死锁。

更多详情

  • await默认情况下捕获一个上下文并在该上下文上恢复。(您可以使用ConfigureAwait(false)覆盖此默认行为并改为在线程池线程上恢复。)
  • Result阻塞当前线程直到Task完成。(您可以使用await异步使用任务以避免阻塞线程。)
  • 一些上下文是单线程上下文;即,它们一次只允许一个线程。例如,ASP.NET Classic 具有单线程请求上下文。(您可以使用Task.Run线程池上下文在线程池线程上运行代码,该线程池上下文不是单线程上下文。)

因此,要获得死锁,您需要有一个await捕获单线程上下文的线程,然后在该上下文中阻塞一个线程(例如,调用Result该任务)。await需要上下文来完成Task,但上下文一次只允许一个线程,并且在该Result上下文中保持线程阻塞直到Task完成。

在您的示例中,您GetJsonAsync在 a 内部调用Task.Run,它在线程池上运行它。因此awaitin GetJsonAsync(以及await传递给的委托中的 in Task.Run)捕获线程池上下文,而不是 ASP.NET 请求线程上下文。然后您的代码调用Result,它确实阻塞了 ASP.NET 请求线程(及其上下文),但由于await不需要该上下文,因此没有死锁。


推荐阅读