【问题标题】:Deadlock when calling an async method inside a non async action在非异步操作中调用异步方法时出现死锁
【发布时间】:2019-10-01 15:15:46
【问题描述】:

我们有一个 Web 应用程序,它由我们的 UI 使用的 Web API 组成。 Web 应用程序安装在不同的服务器上(在我们的客户端安装之前进行测试)。

在一台服务器上,我们通过以下端点获得了死锁:

[HttpGet, Route(Order = 1)]
public IHttpActionResult GetUserDetails(long userKey)
{
    var userService = ResolveService<IUserService>();
    var nameService =  ResolveService<INameService >();

    LoggInfo("Start userService.Get()"); //logged in logs
    var result = userService.Get(userKey);
    this.LoggInfo($"End userService.Get() with id = "{result.Id}); // logged in logs

    try
    {
      LoggInfo($"Start nameService.Get()"); // logged in logs
      result.Name =  nameService.Get(result.Namekey).Result?.Name;
      LoggInfo($"End nameService.Get()"); // not logged in logs
    }
   catch(Exception ex)
   {
     LogError("An error occured in NameService"); // not logged in logs
   }

    return Ok(result);
}

nameService.Get(id) 是一个async 方法:

public async Task<NameDTO> GetAsync(long key)
{
    LogInfo("start NameService.Get()"); // not logged in logs
    var query = GetQuery();
    var name = await query.Select(MapName())
                          .FirstOrDefaultAsync(n => n.Key == key);

    return name;
}

当我删除异步签名时,一切都按预期工作,根据this article,出现死锁是正常的。

你能解释一下为什么这在其他服务器上有效吗?

提前感谢您的帮助

【问题讨论】:

  • 为什么不让GetUserDetails 异步,然后在异步调用中使用正确的等待语法?
  • 这个query.Select(MapName()).FirstOrDefaultAsync(n =&gt; n.Key == key)是不是在有死锁的服务器上同步完成,而在其他服务器上同步完成?
  • @Mason,我知道上面的方法不好,我只是想了解为什么有时有效,有时无效。
  • @PetSerAi,是的,这就是问题所在,请注意查询不需要很多时间(当我在 sql developer 中运行生成的查询时)

标签: c# asynchronous asp.net-web-api


【解决方案1】:

你能解释一下为什么这在其他服务器上有效吗?

为了使the deadlock 发生,需要三个必要部分:

  1. 一次一个线程的上下文,所有服务器都将拥有该上下文(Core ASP.NET 之前的上下文)。
  2. await(不带ConfigureAwait(false)acts asynchronously
  3. 其他地方的代码会阻塞该上下文中的线程,等待async 方法完成。所有服务器也有这个。

最有可能的是,行为上的差异是由于第二部分:一个异步行为的awaitawait first examines its awaitable (e.g., the Task passed to it), and if that awaitable is complete, then it continues executing the async method synchronously.

具体来说,如果从GetAsync 返回的Task&lt;NameDTO&gt; 在调用代码命中.Result 之前完成,就会发生这种情况。这必须非常快,但这可能取决于缓存、网络跃点的速度、代码因负载/邻居/防病毒而减慢的速度等。

【讨论】:

  • 我添加了一些日志,我发现你从来没有打电话,知道吗?
  • @BilelChaouadi:好吧,如果它从不被调用,那么它就永远不会死锁......
  • 那么可能会发生什么,我已经将对 nameService.Get() 的调用包装在 try catch 中,并且我在 catch 块中添加了一些日志,但没有记录错误!!
  • @BilelChaouadi:我不明白你在问什么。
  • 我已经更新了我的问题,问题是我的endPoint没有响应,没有登录nameService并且没有发生错误,似乎nameService.Get从未被命中
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-02-18
  • 1970-01-01
  • 2014-09-12
  • 1970-01-01
  • 2021-11-09
  • 1970-01-01
相关资源
最近更新 更多