Should I always use async/await in ASP.NET Core API Controller

Viewed 10698

As an example I have an ASP.NET Core API controller fetching some data from a service and 2 possible ways to implement the controller method:

With async/await:

[HttpGet]
public async Task<IActionResult> GetSomeDataAsync()
{
   return await someService.GetSomeDataAsync();
}

Without async/await:

[HttpGet]
public Task<IActionResult> GetSomeDataAsync()
{
   return someService.GetSomeDataAsync();
}

Which one of these two is better? The key here is that there is only 1 call to another async method (someService.GetSomeDataAsync()).

4 Answers

According to ASP.NET Core Performance Best Practices from ASP.NET team:

Avoid blocking calls

ASP.NET Core apps should be designed to process many requests simultaneously. Asynchronous APIs allow a small pool of threads to handle thousands of concurrent requests by not waiting on blocking calls. Rather than waiting on a long-running synchronous task to complete, the thread can work on another request.

A common performance problem in ASP.NET Core apps is blocking calls that could be asynchronous. Many synchronous blocking calls lead to Thread Pool starvation and degraded response times.

  • Make hot code paths asynchronous.
  • Call data access, I/O, and long-running operations APIs asynchronously if an asynchronous API is available. Do not use Task.Run to make a synchronus API asynchronous.
  • Make controller/Razor Page actions asynchronous. The entire call stack is asynchronous in order to benefit from async/await patterns.

Avoid synchronous read or write on HttpRequest/HttpResponse body

All I/O in ASP.NET Core is asynchronous. Servers implement the Stream interface, which has both synchronous and asynchronous overloads. The asynchronous ones should be preferred to avoid blocking thread pool threads. Blocking threads can lead to thread pool starvation.

Prefer ReadFormAsync over Request.Form

Use HttpContext.Request.ReadFormAsync instead of HttpContext.Request.Form. HttpContext.Request.Form can be safely read-only with the following conditions:

  • The form has been read by a call to ReadFormAsync, and
  • The cached form value is being read using HttpContext.Request.Form

Optimize data access and I/O

Interactions with a data store and other remote services are often the slowest parts of an ASP.NET Core app. Reading and writing data efficiently is critical for good performance.

  • Do call all data access APIs asynchronously.

The differences between the two - in the "only does one async thing" scenario - are subtle and mostly revolve around exceptions, in particular a: what happens if an exception is thrown instead of returning a faulted task, and b: the particulars of the stack-trace in any exception scenario.

In reality, either will be fine here, but I would keep it simple and use the await syntax in most application code (and a top level web request handler is definitely application code). There is a slight machinery overhead here, but at the application level, it really is nothing, so don't worry about it.

If, however, you were writing a library function that got called hundreds, or thousands, of times per request (network IO, string handling, etc) - then it is worth thinking about more aggressive (and more manual) ways of optimizing the task machinery.

It's difficult to be absolutely sure if the async-await is helping or hindering your controller action method (as you'd need to test it with different loads) but, as a general rule, any controllers that access a database or the file system or another remote API should be asynchronous, and controllers that are performing simple in-memory computations can remain synchronous. Async calls add a little (but only very little) overhead so, if in doubt, go asynchronous.

Related