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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/15021304/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-10 13:49:29  来源:igfitidea点击:

An async/await example that causes a deadlock

c#task-parallel-librarydeadlockasync-awaitc#-5.0

提问by Dror Weiss

I came across some best practices for asynchronous programming using c#'s async/awaitkeywords (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. 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.

... 一些同步上下文是不可重入的和单线程的。这意味着在给定时间只能在上下文中执行一个工作单元。这方面的一个示例是 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();
}

If I try to dissect it myself, the main thread spawns to a new one in MyWebService.GetDataAsync();, but since the main thread awaits there, it waits on the result in 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()?

如果我尝试自己剖析它,主线程MyWebService.GetDataAsync();会在GetDataAsync().Result. 同时,说数据准备好了。为什么主线程不继续它的延续逻辑并从 返回一个字符串结果GetDataAsync()

Can someone please explain me why there is a deadlock in the above example? I'm completely clueless about what the problem is ...

有人可以解释一下为什么上面的例子会出现死锁吗?我完全不知道问题是什么......

采纳答案by cuongle

Take a look at this example, Stephen has a clear answer for you:

看看这个例子,Stephen 已经给你一个明确的答案:

So this is what happens, starting with the top-level method (Button1_Clickfor UI / MyController.Getfor ASP.NET):

  1. The top-level method calls GetJsonAsync(within the UI/ASP.NET context).

  2. GetJsonAsyncstarts the REST request by calling HttpClient.GetStringAsync(still within the context).

  3. GetStringAsyncreturns an uncompleted Task, indicating the REST request is not complete.

  4. GetJsonAsyncawaits the Taskreturned by GetStringAsync. The context is captured and will be used to continue running the GetJsonAsyncmethod later. GetJsonAsyncreturns an uncompleted Task, indicating that the GetJsonAsyncmethod is not complete.

  5. The top-level method synchronously blocks on the Taskreturned by GetJsonAsync. This blocks the context thread.

  6. ... Eventually, the REST request will complete. This completes the Taskthat was returned by GetStringAsync.

  7. The continuation for GetJsonAsyncis now ready to run, and it waits for the context to be available so it can execute in the context.

  8. Deadlock. The top-level method is blocking the context thread, waiting for GetJsonAsyncto complete, and GetJsonAsyncis waiting for the context to be free so it can complete. For the UI example, the "context" is the UI context; for the ASP.NET example, the "context" is the ASP.NET request context. This type of deadlock can be caused for either "context".

所以这就是发生的事情,从顶级方法(Button1_Click用于 UI /MyController.Get用于 ASP.NET)开始:

  1. 顶级方法调用GetJsonAsync(在 UI/ASP.NET 上下文中)。

  2. GetJsonAsync通过调用HttpClient.GetStringAsync(仍在上下文中)启动 REST 请求。

  3. GetStringAsync返回 uncompleted Task,表示 REST 请求未完成。

  4. GetJsonAsync等待由Task返回GetStringAsync。上下文被捕获,稍后将用于继续运行该GetJsonAsync方法。GetJsonAsync返回一个 uncompleted Task,表示该GetJsonAsync方法未完成。

  5. 顶级方法同步阻塞Task返回的GetJsonAsync。这会阻塞上下文线程。

  6. ... 最终,REST 请求将完成。这完成了Task由 返回的GetStringAsync

  7. for 的延续GetJsonAsync现在已准备好运行,它等待上下文可用,以便它可以在上下文中执行。

  8. 死锁。顶层方法正在阻塞上下文线程,等待GetJsonAsync完成,并GetJsonAsync等待上下文空闲以便它可以完成。对于 UI 示例,“上下文”是 UI 上下文;对于 ASP.NET 示例,“上下文”是 ASP.NET 请求上下文。这种类型的死锁可能是由任一“上下文”引起的。

Another link you should read: Await, and UI, and deadlocks! Oh my!

您应该阅读的另一个链接:Await, and UI, and deadlocks!天啊!

回答by marvelTracker

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();
}

回答by Phillip Ngan

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

Deadlock!

僵局!

The deadlock can be broken by provided alternatives to avoid Fact 1 or Fact 2.

可以通过提供的替代方案来打破僵局,以避免事实 1 或事实 2。

  • Avoid 1,4. Instead of blocking the UI thread, use var data = await GetDataAsync(), which allows the UI thread to keep running
  • Avoid 2,3. Queue the continuation of the await to a different thread that is not blocked, e.g. use var data = Task.Run(GetDataAsync).Result, which will post the continuation to the sync context of a threadpool thread. This allows the task returned by GetDataAsync()to complete.
  • 避免 1,4。不是阻塞 UI 线程,而是使用var data = await GetDataAsync(),它允许 UI 线程继续运行
  • 避免 2,3。将 await 的延续排队到另一个未被阻塞的线程,例如 use var data = Task.Run(GetDataAsync).Result,它将把延续发布到线程池线程的同步上下文。这允许完成返回的任务 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().

回答by Herre Kuijpers

I was just fiddling with this issue again in an ASP.NET MVC project. When you want to call asyncmethods from a PartialView, you're not allowed to make the PartialViewasync. You'll get an exception if you do.

我只是在 ASP.NET MVC 项目中再次摆弄这个问题。当您想async从 a调用方法时PartialView,您不允许将PartialViewasync. 如果你这样做,你会得到一个例外。

You can use the following simple workaround in the scenario where you want to call an asyncmethod from a sync method:

在您想要async从同步方法调用方法的场景中,您可以使用以下简单的解决方法:

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

回答by Orace

A work around I came to is to use a Joinextension 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)

我不够深入了解这个解决方案的缺点(如果有的话)