我使用c#的 async
/ await
关键字(我是c#5.0的新手)遇到了一些异步编程的最佳实践 .
给出的建议之一是:
Stability: Know your synchronization contexts
...某些同步上下文是不可重入和单线程的 . 这意味着在给定时间内只能在上下文中执行一个工作单元 . 一个例子是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
中等待结果 . 同时,说数据准备好了 . 为什么't the main thread continue it'的延续逻辑并从 GetDataAsync()
返回字符串结果?
有人可以解释一下为什么上面的例子中存在死锁吗?我完全不知道问题是什么......
5 回答
看看here中的示例,斯蒂芬有明确的答案:
您应该阅读的另一个链接:
Await, and UI, and deadlocks! Oh my!
事实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 . 将等待的继续排队到未被阻止的不同线程,例如使用
var data = Task.Run(GetDataAsync).Result
,它将继续发布到线程池线程的同步上下文 . 这允许GetDataAsync()
返回的任务完成 .这在article by Stephen Toub中得到了很好的解释,大约在他使用
DelayAsync()
示例的一半处 .我只是在一个MVC.Net项目中再次摆弄这个问题 . 如果要从PartialView调用异步方法,则不允许使PartialView异步 . 如果你这样做,你会得到一个例外 .
因此,在您想要从同步方法调用异步方法的场景中,基本上是一个简单的解决方法,您可以执行以下操作:
在调用之前
,清除SynchronizationContext
打电话,这里不再有死锁,等待它完成
恢复SynchronizationContext
例:
另一个要点是你不应该阻止任务,并一直使用async来防止死锁 . 然后它将是所有异步而非同步阻塞 .
我要解决的问题是在请求结果之前对任务使用
Join
扩展方法 .代码看起来像这个:
连接方法是:
我不足以进入该领域,看看这个解决方案的缺点(如果有的话)