简体   繁体   中英

Async calls in Web Api

In asp.net Web API how can I call long running blocking actions asynchronously?

I have a web api controller action that needs to make a relatively (10 seconds, as an example) long running DB call. This seems like a candidate for an asynchronous method. I can offload the long running task onto a new thread, and unblock my asp.net request thread to handle some other requests, so my simplified controller action would look like this:

public async Task<IHttpActionResult> Get()
{
    IEnumerable<Thing> things = await Task.Run(() => DoLongDbCall());
    return Ok(things);
}

I have encountered a couple of blogs ( this one , for example) that suggest this may not be an optimal way to achieve this in asp.net. The author suggests using Task.FromResult() and doing the DB call synchronously, but i cant see how this helps; My request thread is still going to be blocked waiting for the DB call to return.

First, consider what happens with a synchronous call:

public IHttpActionResult Get()
{
  IEnumerable<Thing> things = DoLongDbCall();
  return Ok(things);
}

A request comes in, and ASP.NET grabs a thread pool thread to handle the request. This thread invokes the Get method, which does the work. One thread pool thread is used during the entire request.

Now, let's walk through what happens in the current code (using Task.Run ):

public async Task<IHttpActionResult> Get()
{
  IEnumerable<Thing> things = await Task.Run(() => DoLongDbCall());
  return Ok(things);
}

A request comes in, and ASP.NET grabs a thread pool thread to handle the request. This thread invokes the Get method, which then grabs another thread pool thread to do the work and returns the original thread pool thread back to the thread pool. One thread pool thread is used during the entire request (and two thread pool threads are used for a very brief period of time).

So, the Task.Run code forces an extra thread transition without providing any benefit (the entire point of using async on the server side is to free up threads). This is why I recommend against using Task.Run (or any other way to run work on the thread pool) on ASP.NET.

A proper asynchronous solution would be to use asynchronous DB calls:

public async Task<IHttpActionResult> Get()
{
  IEnumerable<Thing> things = await DoLongDbCallAsync();
  return Ok(things);
}

A request comes in, and ASP.NET grabs a thread pool thread to handle the request. This thread invokes the Get method, which then starts the asynchronous operation and returns the thread pool thread back to the thread pool. Later, when the db call completes, ASP.NET grabs a thread pool thread to finish the request. For most of the request, no thread pool threads are used (one thread pool thread is used for a brief period of time at the beginning and end of the request).

my request thread is still going to be blocked waiting for the DB call to return.

That's not completely true:

Your request will still wait for the long operation to end, but your thread will be free to process other operations.

You must keep in mind that IIS has a reduced number of available threads (especially if running under non server systems) and freeing one that is not needed is always a good thing.

Moreover, if you have shortage of threads, and if you use Task.Run , the asynchronous operation will wait for an available thread, and if you don't release your current one, you'll end up with an horrible deadlock.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM