简体   繁体   English

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

[英]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):

  1. The top-level method calls GetJsonAsync (within the UI/ASP.NET context).顶级方法调用GetJsonAsync (在 UI/ASP.NET 上下文中)。

  2. GetJsonAsync starts the REST request by calling HttpClient.GetStringAsync (still within the context). GetJsonAsync通过调用HttpClient.GetStringAsync (仍在上下文中)启动 REST 请求。

  3. GetStringAsync returns an uncompleted Task , indicating the REST request is not complete. GetStringAsync返回未完成的Task ,表示 REST 请求未完成。

  4. GetJsonAsync awaits the Task returned by GetStringAsync . GetJsonAsync等待GetStringAsync返回的Task The context is captured and will be used to continue running the GetJsonAsync method later.上下文被捕获并将用于稍后继续运行GetJsonAsync方法。 GetJsonAsync returns an uncompleted Task , indicating that the GetJsonAsync method is not complete. GetJsonAsync返回未完成的Task ,表示GetJsonAsync方法未完成。

  5. The top-level method synchronously blocks on the Task returned by GetJsonAsync .顶级方法在GetJsonAsync返回的Task上同步阻塞。 This blocks the context thread.这会阻塞上下文线程。

  6. ... Eventually, the REST request will complete. ...最终,REST 请求将完成。 This completes the Task that was returned by GetStringAsync .这完成了由GetStringAsync返回的Task

  7. 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的延续现在已准备好运行,它等待上下文可用,以便可以在上下文中执行。

  8. Deadlock .死锁 The top-level method is blocking the context thread, waiting for GetJsonAsync to complete, and GetJsonAsync 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! 天啊!

  • Fact 1: GetDataAsync().Result;事实 1: GetDataAsync().Result; will run when the task returned by GetDataAsync() completes, in the meantime it blocks the UI thread将在GetDataAsync()返回的任务完成时运行,同时它会阻塞 UI 线程
  • Fact 2: The continuation of the await ( return result.ToString() ) is queued to the UI thread for execution事实 2:等待的延续( return result.ToString() )排队到 UI 线程执行
  • Fact 3: The task returned by GetDataAsync() will complete when its queued continuation is run事实 3: GetDataAsync()返回的任务将在其排队的延续运行时完成
  • Fact 4: The queued continuation is never run, because the UI thread is blocked (Fact 1)事实 4:排队的延续永远不会运行,因为 UI 线程被阻塞(事实 1)

Deadlock!僵局!

The deadlock can be broken by provided alternatives to avoid Fact 1 or Fact 2.可以通过提供的替代方案来打破僵局,以避免事实 1 或事实 2。

  • Fix 1: Avoid 1,4.修复 1:避免 1,4。 Instead of blocking the UI thread, use var data = await GetDataAsync() , which allows the UI thread to keep running使用var data = await GetDataAsync()代替阻塞 UI 线程,它允许 UI 线程继续运行
  • Fix 2: Avoid 2,3.修复 2:避免 2,3。 Queue the continuation of the await to a different thread that is not blocked, eg use 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方法的场景中,您可以使用以下简单的解决方法:

  1. Before the call, clear the SynchronizationContext在调用之前,清除SynchronizationContext
  2. Do the call, there will be no more deadlock here, wait for it to finish调用吧,这里不会再出现死锁了,等它结束
  3. Restore the 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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM