13

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()).

Muhammad Usman Bashir
  • 1,441
  • 2
  • 14
  • 43
Eric
  • 159
  • 1
  • 1
  • 3
  • 5
    Relevant: [Eliding Async and Await](https://blog.stephencleary.com/2016/12/eliding-async-await.html) – John Wu Apr 11 '20 at 08:00
  • Well, `async` `await` is so popular, because it handles nicely various scenarios, i.e. when there's no need to run asynchronously, it executes synchronously. So I'd advise to use `async`s. But you need to be aware of some overhead, i.e. creating states machine when using that pattern. – Michał Turczyn Apr 11 '20 at 08:02
  • Try throwing an exception in `GetSomeDataAsync` and compare the stack traces. I think one is more readable than the other. – John Wu Apr 11 '20 at 08:22

4 Answers4

15

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.

  • Do make hot code paths asynchronous.
  • Do 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 synchronous API asynchronous.
  • Do 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.
Mohsen Esmailpour
  • 11,224
  • 3
  • 45
  • 66
4

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.

Marc Gravell
  • 1,026,079
  • 266
  • 2,566
  • 2,900
1

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.

benjamin
  • 1,364
  • 1
  • 14
  • 26
-1

Its best practice to return async methods... async.

This is called async all the way, see the msdn guidelines:

https://learn.microsoft.com/en-us/archive/msdn-magazine/2013/march/async-await-best-practices-in-asynchronous-programming

sommmen
  • 6,570
  • 2
  • 30
  • 51
  • its of course always good practice to make sure you actually need an async method and it could not just be sync, but that is another topic. – sommmen Apr 11 '20 at 07:51
  • 3
    I don't think "async-all-the-way" means what you think it means. Just by leaving out the `async` and `await` keywords doesn't suddenly not make this async anymore. It still returns an awaitable task. Now I have to admit I'm not completely sure but I think all the async adds here is add another (unneeded) statemachine behind the scenes. Now you could say "yeah but it's just better to explicitly add the async keyword to all async methods". That's certainly a valid idea, but personal preference. –  Apr 11 '20 at 08:04
  • @Knoop i find that working with several teammembers its often more fruiteful to be a little bit more explicit than gain an inch of performance. Tbh the whole TPL library is already complex so adding even more for such small gain seems weird to me. But i gues that if you have a larger understanding of TPL like you seem to have you rather choose a different way. Thanks for the comment! – sommmen Apr 11 '20 at 08:16
  • That was not my point. As I said from the standpoint of working in a team and for clear code in general I completely agree it's certainly a valid choice (one I make as well), but that's a coding guideline choice not a technical one. However your answer seems to imply that it's actually a technical choice since async-all-the-way means don't use blocking code in async methods, which is absolutely not the case here. Both versions are async and adhere to the async-all-the-way principle –  Apr 11 '20 at 08:28