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

问题描述 投票:0回答:1

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

c# async-await deadlock
1个回答
3
投票

GetJsonAsync(...)).Result本身不会造成僵局。当从单线程上下文调用时,如果该任务的System.Threading.Tasks.Task.Run(async () => await GetJsonAsync(...)).Result也需要该上下文,则会导致死锁。

Result

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

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

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

© www.soinside.com 2019 - 2024. All rights reserved.