使用 Async Await 是否可以避免线程耗尽?

Does Using Async Await Avoid Thread Exhaustion?

我们正在解决 .NET Core API 端点上的以下性能问题:

  1. 在较小的负载下,端点始终 returns 小于 500MS
  2. 当我们从 3 个浏览器访问端点时,每秒一个请求,它 变得越来越慢 (在添加第三个浏览器进行调用的一分钟内,响应时间下降到50,000MS 或更糟。
  3. 每个额外的浏览器 添加 API 使用的线程,例如40 个线程基础,第二个浏览器命中端点导致 52 个线程,第三个峰值为 70,依此类推。
  4. 加载一个端点时,整个API returns 缓慢(所有端点)。这是我考虑 "thread exhaustion" 以及第 3 点的主要原因。

代码目前如下所示:

    public IActionResult GetPresentationByEvent(int eventid)
    {
      return Authorized(authDto =>
      {
        var eventList = _eventService.GetPresentationByEvent(eventid);
        return Ok(eventList)
      })
    }

我的理论是 return Authorized(authDto => 持有一个线程直到它 returns,导致线程耗尽。

    public async Task<IActionResult> GetPresentationByEvent(int eventid)
    {
      return Authorized(async authDto =>
      {
        Task<List<whatever>> eventList = _eventService.GetPresentationByEvent(eventid);
        return Ok(eventList)
      }
    }

Authorized 是第三方库的一部分,所以我不能轻易测试这个。想知道这是否看起来像 problem/solution.

是的,async await 可以减少线程耗尽。简而言之,当您生成的任务多于 ThreadPool 可以处理的数量时,就会出现线程耗尽。

您可以在此处查看一些细微的细节:Thread starvation and queuing

你唯一需要记住的是你永远不应该在任务中阻塞。这意味着使用 async await 调用异步代码(永远不要在未完成的任务上使用 .Wait 或 .Result)。

如果您使用一些不使用异步等待模式的阻塞代码,您必须在专用线程(而不是任务线程队列)上生成它。

My theory is that return Authorized(authDto => holds a thread until it returns, leading to thread exhaustion.

是的。您可以通过查看其 return 值轻松判断方法是否同步。 IActionResult 不是等待类型,因此此方法将 运行 同步。

Authorized is part of a third-party library, so I can't test this easily. Would like to know if this looks like a likely problem/solution.

可能吧。这完全取决于 Authorized 是否可以处理异步委托。如果可以,那么这样的事情就可以了:

public async Task<IActionResult> GetPresentationByEvent(int eventid)
{
  return Authorized(async authDto =>
  {
    Task<List<whatever>> eventList = _eventService.GetPresentationByEventAsync(eventid);
    return Ok(await eventList);
  });
}

注:

  1. 任务在传递给 Ok 或其他助手之前应该 awaited。
  2. 这引入了 GetPresentationByEventAsync,假设您的数据访问代码可以异步。

由于使 GetPresentationByEvent 异步可能需要一些工作,因此值得在尝试此之前调查 Authorized 是否可以采用异步委托。

Does Using Async Await Avoid Thread Exhaustion?

是也不是。异步代码(包括 async/await)确实使用较少的线程,因为它避免了阻塞线程。但是,仍然有一个限制。线程耗尽仍然是可能的,因为异步代码需要一个空闲线程才能完成。使用异步代码,通常您可以在 运行 陷入线程耗尽等可伸缩性问题之前实现一个或两个更大的可伸缩性。

有关 async ASP.NET 的更多概念信息,请参阅 this MSDN article