Search code examples
c#multithreadingasp.net-core-2.1non-exhaustive-patterns

Does Using Async Await Avoid Thread Exhaustion?


We are troubleshooting the following performance issues on a .NET Core API endpoint:

  1. The endpoint consistently returns in less than 500MS under minor load.
  2. When we hit the endpoint from 3 browsers, with one request a second, it gets progressively slower (within a minute of adding a third browser making calls, response times drops to 50,000MS or worse.
  3. Each additional browser adds threads used by the API, e.g. 40 threads base, 2nd browser hitting endpoint leads to 52 threads, third spikes to 70, and so on.
  4. When one endpoint is loaded, the entire API returns slowly (all endpoints). This is my main reason for thinking "thread exhaustion", along with point #3.

The code currently looks like this:

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

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

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

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.


Solution

  • Yes async await can reduce thread exhaustion. In a few words thread exhaustion arise when you generate more tasks than your ThreadPool can handle.

    There are subtle specifities that you can check here : Thread starvation and queuing

    The only thing that you have to keep in mind on your side is that you should never block inside a task. This implies calling asynchronous code with async await (and never using .Wait or .Result on a non finished task).

    If you use some blocking code wich is not using the async await pattern you have to spawn it on a dedicated thread (not the task thread queue).