[英]An async/await example that causes a deadlock
我遇到了一些使用 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()
返回字符串结果?
有人可以解释一下为什么上面的例子中会出现死锁吗? 我完全不知道问题是什么......
看看这个例子,斯蒂芬给你一个明确的答案:
所以这就是发生的事情,从顶级方法开始(
Button1_Click
用于 UI /MyController.Get
用于 ASP.NET):
顶级方法调用
GetJsonAsync
(在 UI/ASP.NET 上下文中)。
GetJsonAsync
通过调用HttpClient.GetStringAsync
(仍在上下文中)启动 REST 请求。
GetStringAsync
返回未完成的Task
,表示 REST 请求未完成。
GetJsonAsync
等待GetStringAsync
返回的Task
。 上下文被捕获并将用于稍后继续运行GetJsonAsync
方法。GetJsonAsync
返回未完成的Task
,表示GetJsonAsync
方法未完成。顶级方法在
GetJsonAsync
返回的Task
上同步阻塞。 这会阻塞上下文线程。...最终,REST 请求将完成。 这完成了由
GetStringAsync
返回的Task
。
GetJsonAsync
的延续现在已准备好运行,它等待上下文可用,以便可以在上下文中执行。死锁。 顶级方法正在阻塞上下文线程,等待
GetJsonAsync
完成,而GetJsonAsync
正在等待上下文空闲以便它可以完成。 对于 UI 示例,“上下文”是 UI 上下文; 对于 ASP.NET 示例,“上下文”是 ASP.NET 请求上下文。 这种类型的死锁可能是由任一“上下文”引起的。
您应该阅读的另一个链接: 等待、UI 和死锁! 天啊!
GetDataAsync().Result;
将在GetDataAsync()
返回的任务完成时运行,同时它会阻塞 UI 线程return result.ToString()
)排队到 UI 线程执行GetDataAsync()
返回的任务将在其排队的延续运行时完成可以通过提供的替代方案来打破僵局,以避免事实 1 或事实 2。
var data = await GetDataAsync()
代替阻塞 UI 线程,它允许 UI 线程继续运行var data = Task.Run(GetDataAsync).Result
,这会将延续发布到线程池线程的同步上下文。 这允许GetDataAsync()
返回的任务完成。 这在Stephen Toub 的一篇文章中得到了很好的解释,大约在他使用DelayAsync()
示例的一半的地方。
我只是在 ASP.NET MVC 项目中再次摆弄这个问题。 当您想从PartialView
调用async
方法时,不允许将PartialView
设为async
。 如果你这样做,你会得到一个例外。
在要从同步方法调用async
方法的场景中,您可以使用以下简单的解决方法:
SynchronizationContext
SynchronizationContext
例子:
public ActionResult DisplayUserInfo(string userName)
{
// trick to prevent deadlocks of calling async method
// and waiting for on a sync UI thread.
var syncContext = SynchronizationContext.Current;
SynchronizationContext.SetSynchronizationContext(null);
// this is the async call, wait for the result (!)
var model = _asyncService.GetUserInfo(Username).Result;
// restore the context
SynchronizationContext.SetSynchronizationContext(syncContext);
return PartialView("_UserInfo", model);
}
另一个要点是你不应该阻塞任务,并且一直使用异步来防止死锁。 那么这将是所有异步而不是同步阻塞。
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();
}
我想到的一个解决方法是在询问结果之前对任务使用Join
扩展方法。
代码如下所示:
public ActionResult ActionAsync()
{
var task = GetDataAsync();
task.Join();
var data = task.Result;
return View(data);
}
join方法在哪里:
public static class TaskExtensions
{
public static void Join(this Task task)
{
var currentDispatcher = Dispatcher.CurrentDispatcher;
while (!task.IsCompleted)
{
// Make the dispatcher allow this thread to work on other things
currentDispatcher.Invoke(delegate { }, DispatcherPriority.SystemIdle);
}
}
}
我对这个领域的了解还不够,看不到这个解决方案的缺点(如果有的话)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.