[英]An async/await example that causes a deadlock
I came across some best practices for asynchronous programming using c#'s async
/ await
keywords (I'm new to c# 5.0).我遇到了一些使用 c# 的
async
/ await
关键字进行异步编程的最佳实践(我是 c# 5.0 的新手)。
One of the advices given was the following:给出的建议之一如下:
Stability: Know your synchronization contexts稳定性:了解您的同步上下文
... Some synchronization contexts are non-reentrant and single-threaded. ...一些同步上下文是不可重入的和单线程的。 This means only one unit of work can be executed in the context at a given time.
这意味着在给定时间只能在上下文中执行一个工作单元。 An example of this is the Windows UI thread or the ASP.NET request context.
这方面的一个示例是 Windows UI 线程或 ASP.NET 请求上下文。 In these single-threaded synchronization contexts, it's easy to deadlock yourself.
在这些单线程同步上下文中,很容易让自己陷入死锁。 If you spawn off a task from a single-threaded context, then wait for that task in the context, your waiting code may be blocking the background task.
如果您从单线程上下文中生成任务,然后在上下文中等待该任务,您的等待代码可能会阻塞后台任务。
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();
}
If I try to dissect it myself, the main thread spawns to a new one in MyWebService.GetDataAsync();
如果我尝试自己剖析它,主线程会在
MyWebService.GetDataAsync();
, but since the main thread awaits there, it waits on the result in GetDataAsync().Result
. ,但由于主线程在那里等待,它在
GetDataAsync().Result
中等待结果。 Meanwhile, say the data is ready.同时,说数据准备好了。 Why doesn't the main thread continue it's continuation logic and returns a string result from
GetDataAsync()
?为什么主线程不继续它的延续逻辑并从
GetDataAsync()
返回字符串结果?
Can someone please explain me why there is a deadlock in the above example?有人可以解释一下为什么上面的例子中会出现死锁吗? I'm completely clueless about what the problem is ...
我完全不知道问题是什么......
Take a look at this example , Stephen has a clear answer for you:看看这个例子,斯蒂芬给你一个明确的答案:
So this is what happens, starting with the top-level method (
Button1_Click
for UI /MyController.Get
for ASP.NET):所以这就是发生的事情,从顶级方法开始(
Button1_Click
用于 UI /MyController.Get
用于 ASP.NET):
The top-level method calls
GetJsonAsync
(within the UI/ASP.NET context).顶级方法调用
GetJsonAsync
(在 UI/ASP.NET 上下文中)。
GetJsonAsync
starts the REST request by callingHttpClient.GetStringAsync
(still within the context).GetJsonAsync
通过调用HttpClient.GetStringAsync
(仍在上下文中)启动 REST 请求。
GetStringAsync
returns an uncompletedTask
, indicating the REST request is not complete.GetStringAsync
返回未完成的Task
,表示 REST 请求未完成。
GetJsonAsync
awaits theTask
returned byGetStringAsync
.GetJsonAsync
等待GetStringAsync
返回的Task
。 The context is captured and will be used to continue running theGetJsonAsync
method later.上下文被捕获并将用于稍后继续运行
GetJsonAsync
方法。GetJsonAsync
returns an uncompletedTask
, indicating that theGetJsonAsync
method is not complete.GetJsonAsync
返回未完成的Task
,表示GetJsonAsync
方法未完成。The top-level method synchronously blocks on the
Task
returned byGetJsonAsync
.顶级方法在
GetJsonAsync
返回的Task
上同步阻塞。 This blocks the context thread.这会阻塞上下文线程。
... Eventually, the REST request will complete.
...最终,REST 请求将完成。 This completes the
Task
that was returned byGetStringAsync
.这完成了由
GetStringAsync
返回的Task
。The continuation for
GetJsonAsync
is now ready to run, and it waits for the context to be available so it can execute in the context.GetJsonAsync
的延续现在已准备好运行,它等待上下文可用,以便可以在上下文中执行。Deadlock .
死锁。 The top-level method is blocking the context thread, waiting for
GetJsonAsync
to complete, andGetJsonAsync
is waiting for the context to be free so it can complete.顶级方法正在阻塞上下文线程,等待
GetJsonAsync
完成,而GetJsonAsync
正在等待上下文空闲以便它可以完成。 For the UI example, the "context" is the UI context;对于 UI 示例,“上下文”是 UI 上下文; for the ASP.NET example, the "context" is the ASP.NET request context.
对于 ASP.NET 示例,“上下文”是 ASP.NET 请求上下文。 This type of deadlock can be caused for either "context".
这种类型的死锁可能是由任一“上下文”引起的。
Another link you should read: Await, and UI, and deadlocks!您应该阅读的另一个链接: 等待、UI 和死锁! Oh my!
天啊!
GetDataAsync().Result;
GetDataAsync().Result;
will run when the task returned by GetDataAsync()
completes, in the meantime it blocks the UI threadGetDataAsync()
返回的任务完成时运行,同时它会阻塞 UI 线程return result.ToString()
) is queued to the UI thread for executionreturn result.ToString()
)排队到 UI 线程执行GetDataAsync()
will complete when its queued continuation is runGetDataAsync()
返回的任务将在其排队的延续运行时完成 The deadlock can be broken by provided alternatives to avoid Fact 1 or Fact 2.可以通过提供的替代方案来打破僵局,以避免事实 1 或事实 2。
var data = await GetDataAsync()
, which allows the UI thread to keep runningvar data = await GetDataAsync()
代替阻塞 UI 线程,它允许 UI 线程继续运行var data = Task.Run(GetDataAsync).Result
, which will post the continuation to the sync context of a threadpool thread.var data = Task.Run(GetDataAsync).Result
,这会将延续发布到线程池线程的同步上下文。 This allows the task returned by GetDataAsync()
to complete.GetDataAsync()
返回的任务完成。 This is explained really well in an article by Stephen Toub , about half way down where he uses the example of DelayAsync()
.这在Stephen Toub 的一篇文章中得到了很好的解释,大约在他使用
DelayAsync()
示例的一半的地方。
I was just fiddling with this issue again in an ASP.NET MVC project.我只是在 ASP.NET MVC 项目中再次摆弄这个问题。 When you want to call
async
methods from a PartialView
, you're not allowed to make the PartialView
async
.当您想从
PartialView
调用async
方法时,不允许将PartialView
设为async
。 You'll get an exception if you do.如果你这样做,你会得到一个例外。
You can use the following simple workaround in the scenario where you want to call an async
method from a sync method:在要从同步方法调用
async
方法的场景中,您可以使用以下简单的解决方法:
SynchronizationContext
SynchronizationContext
SynchronizationContext
SynchronizationContext
Example:例子:
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);
}
Another main point is that you should not block on Tasks, and use async all the way down to prevent deadlocks.另一个要点是你不应该阻塞任务,并且一直使用异步来防止死锁。 Then it will be all asynchronous not synchronous blocking.
那么这将是所有异步而不是同步阻塞。
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();
}
A work around I came to is to use a Join
extension method on the task before asking for the result.我想到的一个解决方法是在询问结果之前对任务使用
Join
扩展方法。
The code look's like this:代码如下所示:
public ActionResult ActionAsync()
{
var task = GetDataAsync();
task.Join();
var data = task.Result;
return View(data);
}
Where the join method is: 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);
}
}
}
I'm not enough into the domain to see the drawbacks of this solution (if any)我对这个领域的了解还不够,看不到这个解决方案的缺点(如果有的话)
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.