为什么在同步模式下访问Task.Result不会导致死锁?

时间:2019-04-24 10:47:42

标签: c# async-await deadlock

众所周知,在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不是。

1 个答案:

答案 0 :(得分:3)

Result本身不会导致死锁 。当从单线程上下文中调用时,它会导致死锁。 ,如果该任务还有一个await,也需要该上下文。

More details

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

因此,要陷入僵局,您需要拥有一个await来捕获单线程上下文,然后在该上下文中阻塞线程(例如,在该任务上调用Result)。 await需要上下文来完成Task,但是上下文一次仅允许一个线程,并且Result在该上下文中保持线程阻塞,直到{{1} }完成。

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