108

我遇到了一些使用 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()

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

4

5 回答 5

89

看看这个例子,斯蒂芬给你一个明确的答案:

所以这就是发生的事情,从顶级方法开始(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 请求将完成。这完成了TaskGetStringAsync.

  7. 继续 forGetJsonAsync现在可以运行了,它等待上下文可用,以便可以在上下文中执行。

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

您应该阅读的另一个链接:等待、UI 和死锁!天啊!

于 2013-02-22T10:37:37.090 回答
27
  • 事实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。将 await 的延续排队到另一个未被阻塞的线程,例如 use var data = Task.Run(GetDataAsync).Result,这会将延续发布到线程池线程的同步上下文中。这允许完成返回的任务 GetDataAsync()

这在Stephen Toub 的一篇文章中得到了很好的解释,大约在他使用DelayAsync().

于 2017-05-11T10:01:23.073 回答
26

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

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

  1. 通话前,清除SynchronizationContext
  2. 调用吧,这里不会再出现死锁了,等它结束
  3. 恢复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);
}
于 2018-06-29T09:12:48.803 回答
3

另一个要点是你不应该阻塞任务,并且一直使用异步来防止死锁。那么这将是所有异步而不是同步阻塞。

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();
}
于 2016-04-19T22:51:35.630 回答
0

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

我对这个领域的了解还不够,看不到这个解决方案的缺点(如果有的话)

于 2018-09-26T09:58:07.000 回答