当前位置: 首页 > 知识库问答 >
问题:

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

吴才俊
2023-03-14

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

如果我自己尝试剖析它,主线程会在<code>MyWebService中生成一个新线程。GetDataAsync(),但由于主线程在那里等待,因此它在GetDataAsync()中等待结果。result。同时,说数据准备好了。为什么主线程不继续它的继续逻辑并从<code>GetDataAsync()

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

共有3个答案

雷骁
2023-03-14

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

在要从同步方法调用异步方法的方案中,可以使用以下简单的解决方法:

  1. 在调用之前,清除SynChronizationContext
  2. 做通话,这里不会再有死锁,等它完成
  3. 恢复同步上下文

示例:

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);
}
林项明
2023-03-14
  • 事实1:<code>GetDataAsync()。结果 GetDataAsync()返回的任务完成时,code>将运行,同时它会阻塞UI线程
  • 事实2:等待的继续(返回结果。ToString())被排队到UI线程执行
  • 事实3:GetDataAsync()返回的任务将在队列继续运行时完成。
  • 事实4:队列继续从未运行,因为UI线程被阻塞(事实1)

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

    < li >修复1:避免1,4。请使用< code > var data = await getdata async()而不是阻塞UI线程,这将允许UI线程继续运行 < li >修复2:避免2,3。将await的延续排队到另一个未被阻塞的线程,例如使用< code>var data = Task。运行(GetDataAsync)。Result,它将把continuation发送到threadpool线程的同步上下文中。这允许< code>GetDataAsync()返回的任务完成。

Stephen Toub的一篇文章很好地解释了这一点,大约在他使用DelayAsync()示例的一半处。

公西凯捷
2023-03-14

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

这就是发生的情况,从顶层方法开始(Button1_Click对于UI/MyController.Get,对于ASP.NET):

>

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

    < code>GetJsonAsync通过调用< code>HttpClient来启动REST请求。GetStringAsync(仍在上下文中)。

    < code>GetStringAsync返回未完成的< code >任务,表示REST请求未完成。

    < code>GetJsonAsync等待< code>GetStringAsync返回的< code>Task。该上下文被捕获,并将在以后用于继续运行< code>GetJsonAsync方法。< code>GetJsonAsync返回未完成的< code >任务,表示< code>GetJsonAsync方法未完成。

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

    ...最终,REST 请求将完成。这样就完成了由字符串异步返回的任务

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

    僵局。顶层方法正在阻塞上下文线程,等待< code>GetJsonAsync完成,而< code>GetJsonAsync正在等待上下文释放,以便它可以完成。对于UI示例,“上下文”是UI上下文;对于ASP.NET的例子,“上下文”是ASP.NET请求上下文。这种类型的死锁可能由任一“上下文”引起。

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

  •  类似资料:
    • 我试图在react/electron项目中使用async/await,但它不起作用。我想要的是获取docker容器状态列表。但是安慰。日志(列表)返回未定义的。 有人能帮我吗?:)

    • 我在运行以下代码时遇到了一个奇怪的问题: 首先,代码是用OpenJDK-11和OpenJFX-11编译的,在Windows中运行良好(即退出call)。 然而,如果我在Linux(特别是Ubuntu20.04)上运行这个程序,调用会锁定线程,程序永远不会退出。注释出调用将使其重新正常工作。 我只是在(这是SystemLookAndFeel返回的结果)或者我在这里做了什么错误/意外的事情?

    • 我试图使用C++11的std::condition_variable,但是当我试图从第二个线程锁定与其关联的unique_lock时,我得到一个异常“资源死锁已避免”。创建它的线程可以锁定和解锁它,但不能锁定第二个线程,即使我非常肯定unique_lock不应该在第二个线程试图锁定它的地方已经锁定。 FWIW我在Linux中使用gcc4.8.1和-std=gnu++11。 我已经围绕conditi

    • 我有以下代码,这是通过Express调用的API。我需要返回一个引号数组,以及与这些引号相关联的部分数组。为此,我使用async/wait,它在我的引号变量中正常工作,但我在部件变量中得到一个空数组。 我相信这与部分结果是挂起的promise有关,因为当我在循环中记录我的结果时,我得到了适当的值。 我怀疑我一定是误解了async/await是如何工作的,或者promises/Promise是如何工

    • 我正在尝试将数据库调用移出控制器,以清理并使其可测试。当它们在控制器中时,一切都会顺利进行。我将它们移出控制器,并添加了一个异步,以确保我们等待。否则,我将调用的中的函数。现在,一旦我使用async/await,控制器中的函数就会认为没有用户,因为它没有等待。 有几个关于异步等待的SO问题,但我没有找到一个解决我的问题。我确实验证了返回了我的用户,并添加了控制台日志来显示路径。 节点猫鼬异步等待似

    • 我需要在账户之间实现一些基本的资金转移逻辑。为了保持转账的一致性,我利用了同步锁。 这是帐户对象: 这是在transfer worker类中: 我的问题是,如果source Account没有足够的资金,我计划等待该线程,直到它从其他运营中获得资金。对于这个,我一直在等待源帐户。但它以死锁告终,因为目标帐户锁仍在我的帐户上。我怎样才能做到这一点?你能告诉我解决这个问题的正确方法吗? 这就是我试图推