导致死锁的异步/等待示例

我碰到一些使用c#的async / await关键字进行异步编程的最佳实践(我是c#5.0的新手)。

其中一个建议是:

稳定性:了解您的同步上下文

…一些同步上下文是不可重入和单线程的。 这意味着在给定的时间只能在上下文中执行一个工作单元。 这个例子是Windows UI线程或ASP.NET请求上下文。 在这些单线程的同步上下文中,自己很容易死锁。 如果你从一个单线程上下文中产生一个任务,那么等待上下文中的任务,你的等待代码可能会阻塞后台任务。

public ActionResult ActionAsync() { // DEADLOCK: this blocks on the async task var data = GetDataAsync().Result; return View(data); } private async Task<string> GetDataAsync() { // a very simple async method var result = await MyWebService.GetDataAsync(); return result.ToString(); } 

如果我自己尝试剖析它,主线程会在“MyWebService.GetDataAsync();”中生成一个新线程,但是由于主线程在那里等待,它将等待“GetDataAsync()。Result”的结果。 同时,说数据已经准备好了。 为什么主线程不继续它的继续逻辑,并从GetDataAsync()返回字符串结果?

有人可以解释一下为什么在上面的例子中有一个僵局? 我完全不知道问题是什么

看看这里的例子,史蒂芬对你有明确的答案:

所以这就是发生了什么,从顶级方法(用于UI的UI / MyController.Get的Button1_Click)开始:

  1. 顶级方法调用GetJsonAsync(在UI / ASP.NET上下文中)。

  2. GetJsonAsync通过调用HttpClient.GetStringAsync(仍在上下文中)来启动REST请求。

  3. GetStringAsync返回一个未完成的Task,表示REST请求没有完成。

  4. GetJsonAsync等待GetStringAsync返回的任务。 上下文被捕获,并将用于稍后继续运行GetJsonAsync方法。 GetJsonAsync返回一个未完成的任务,表明GetJsonAsync方法不完整。

  5. 顶级方法同步阻止GetJsonAsync返回的任务。 这阻止了上下文线程。

  6. 最终,REST请求将完成。 这完成了由GetStringAsync返回的任务。

  7. GetJsonAsync的延续现在已经准备好运行,并且等待上下文可用,以便它可以在上下文中执行。

  8. 僵局。 顶级方法阻塞上下文线程,等待GetJsonAsync完成,并且GetJsonAsync正在等待上下文被释放,以便完成。 对于UI示例,“上下文”是UI上下文; 对于ASP.NET示例,“上下文”是ASP.NET请求上下文。 这种类型的死锁可以造成任何“上下文”。

您应该阅读的另一个链接:

等待,UI和死锁! 天啊!

另一个要点是,你不应该阻止任务,并使用异步,以防止死锁。 那么这将是所有异步不同步阻塞。

 public async Task<ActionResult> ActionAsync() { var data = await GetDataAsync(); return View(data); } private async Task<string> GetDataAsync() { // a very simple async method var result = await MyWebService.GetDataAsync(); return result.ToString(); } 
  • 事实1: GetDataAsync().Result; 将在GetDataAsync()返回的任务完成时运行,同时阻塞UI线程
  • 事实2:await的继续( return result.ToString() )排队到UI线程执行
  • 事实3: GetDataAsync()返回的任务将在排队延续运行时完成
  • 事实4:由于UI线程被阻塞(事实1),排队的延续从不运行,

僵局!

僵局可以通过提供替代方法来打破事实1或事实2。

  • 避免1,4。 而不是阻塞UI线程,使用var data = await GetDataAsync() ,它允许UI线程继续运行
  • 避免2,3。 将await的继续排队到不被阻塞的另一个线程,例如使用var data = Task.Run(GetDataAsync).Result ,它将延续到线程池线程的同步上下文。 这允许完成由GetDataAsync()返回的任务。

在Stephen Toub的一篇文章中 ,他使用DelayAsync()的例子说明了这DelayAsync()