导致死锁的异步/等待示例
我碰到一些使用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)开始:
顶级方法调用GetJsonAsync(在UI / ASP.NET上下文中)。
GetJsonAsync通过调用HttpClient.GetStringAsync(仍在上下文中)来启动REST请求。
GetStringAsync返回一个未完成的Task,表示REST请求没有完成。
GetJsonAsync等待GetStringAsync返回的任务。 上下文被捕获,并将用于稍后继续运行GetJsonAsync方法。 GetJsonAsync返回一个未完成的任务,表明GetJsonAsync方法不完整。
顶级方法同步阻止GetJsonAsync返回的任务。 这阻止了上下文线程。
最终,REST请求将完成。 这完成了由GetStringAsync返回的任务。
GetJsonAsync的延续现在已经准备好运行,并且等待上下文可用,以便它可以在上下文中执行。
僵局。 顶级方法阻塞上下文线程,等待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()
。