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

4
我们正在解决一个与 .NET Core API 端点相关的性能问题:
  1. 在轻载下,端点始终在小于 500MS 内返回。
  2. 当我们从三个浏览器发出请求时,每秒一个请求,在不到一分钟内(第三个浏览器开始调用),响应时间会变得越来越慢,直至达到 50,000MS 或更差。
  3. 每个额外的浏览器都增加了API使用的线程数,例如:40个线程的基础上,第二个浏览器访问端点会增加到52个线程,第三个则增加到70个及以上。
  4. 当一个端点负载过大时,整个API的返回速度都会变慢(所有端点)。这是我认为线程耗尽的主要原因,以及上述第3点。

目前代码如下:

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

我的理论是,return Authorized(authDto =>会阻塞线程直到它返回,导致线程耗尽。

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

Authorized 是第三方库的一部分,所以我不能轻易地测试这个。想知道这是否看起来像一个可能的问题/解决方案。


4
如果调用是异步的,一直到数据库或文件访问,这种方法才能起作用。请参考https://blog.stephencleary.com/2013/11/there-is-no-thread.html。 - keuleJ
3
“third spikes to 70”这句话很奇怪。那些线程在做什么?你应该有一个基本的线程数,在预热之后,每个同时请求只应增加一个线程。 - Stephen Cleary
我正在研究它。我已经更改了调用,返回一个新的列表而不需要访问数据库/进行任何身份验证。每个浏览器也建立了一个SignalR连接,所以也许我在那里启动了线程,端点减速只是一个副作用。现在将进行测试。感谢回复,它们真的帮助我思考这个问题。 - VSO
1
@StephenCleary 让我纠正一下 - 每个浏览器都在运行一个负载测试函数,每秒向API发出一个请求。因此,从这个角度来看,我认为线程数是有意义的。顺便说一句 - 这不是SignalR,使用SignalR连接而没有命中这个端点,整个API的响应也很好。现在更加迷失了,因为使端点返回一个空列表仍然无法处理每秒3个请求(来自3个浏览器)。 - VSO
2个回答

6

使用async await确实可以减少线程耗尽问题。简而言之,当您生成的任务超过线程池的处理能力时,就会出现线程耗尽问题。

这里有一些微妙的细节,您可以在这里查看:线程饥饿和排队。

您需要记住的唯一一件事是,在任务内部不要阻塞。这意味着使用异步代码时应该调用异步等待(async await),而不是在未完成的任务上使用.Wait或.Result。

如果您正在使用一些未使用异步等待模式的阻塞代码,则必须将其放置在专用线程上(而不是任务线程队列中)。


4
我的理论是,return Authorized(authDto =>)会持有线程直到返回,从而导致线程耗尽。
是的。只需查看其返回值,就可以轻松判断方法是否同步。 IActionResult不是可等待类型,因此此方法将同步运行。
可能。这完全取决于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或其他辅助方法之前,应该使用await等待任务完成。
  2. 这里引入GetPresentationByEventAsync,假定您的数据访问代码可以异步化。

由于使GetPresentationByEvent异步可能需要一些工作,在尝试此操作之前值得探究Authorized是否可以接受异步委托。

使用异步等待是否避免线程耗尽?

是和否。异步代码(包括async/await)确实使用更少的线程,因为它避免了阻塞线程。但是,仍然存在限制。线程耗尽仍然是可能的,因为异步代码需要一个空闲的线程来完成。通常情况下,使用异步代码可以在出现可伸缩性问题(如线程耗尽)之前,实现一到两个数量级的更高伸缩性。

有关async ASP.NET的更多概念信息,请参见此MSDN文章


感谢您的回复。看起来那部分是问题的一部分,但是还有其他事情发生在我的问题范围之外,我需要深入挖掘。由于他先回答了,所以我接受了他的答案,但我非常感谢您的帮助。 - VSO

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接