Asynchronous methods of ApiController -- what's the profit? When to use?

Rustem Mustafin picture Rustem Mustafin · Nov 12, 2013 · Viewed 18k times · Source

(This probably duplicates the question ASP.NET MVC4 Async controller - Why to use?, but about webapi, and I do not agree with answers in there)

Suppose I have a long running SQL request. Its data should be than serialized to JSON and sent to browser (as a response for xhr request). Sample code:

public class DataController : ApiController
{
    public Task<Data> Get()
    {
        return LoadDataAsync(); // Load data asynchronously?
    }
}

What actually happens when I do $.getJson('api/data', ...) (see this poster http://www.asp.net/posters/web-api/ASP.NET-Web-API-Poster.pdf):

  1. [IIS] Request is accepted by IIS.
  2. [IIS] IIS waits for one thread [THREAD] from the managed pool (http://msdn.microsoft.com/en-us/library/0ka9477y(v=vs.110).aspx) and starts work in it.
  3. [THREAD] Webapi Creates new DataController object in that thread, and other classes.
  4. [THREAD] Uses task-parallel lib to start a sql-query in [THREAD2]
  5. [THREAD] goes back to managed pool, ready for other processing
  6. [THREAD2] works with sql driver, reads data as it ready and invokes [THREAD3] to reply for xhr request
  7. [THREAD3] sends response.

Please, feel free to correct me, if there's something wrong.

In the question above, they say, the point and profit is, that [THREAD2] is not from The Managed Pool, however MSDN article (link above) says that

By default, parallel library types like Task and Task<TResult> use thread pool threads to run tasks.

So I make a conclusion, that all THREE THREADS are from managed pool.

Furthermore, if I used synchronous method, I would still keep my server responsive, using only one thread (from the precious thread pool).

So, what's the actual point of swapping from 1 thread to 3 threads? Why not just maximize threads in thread pool?

Are there any clearly useful ways of using async controllers?

Answer

Stephen Cleary picture Stephen Cleary · Nov 12, 2013

I think the key misunderstanding is around how async tasks work. I have an async intro on my blog that may help.

In particular, a Task returned by an async method does not run any code. Rather, it is just a convenient way to notify callers of the result of that method. The MSDN docs you quoted only apply to tasks that actually run code, e.g., Task.Run.

BTW, the poster you referenced has nothing to do with threads. Here's what happens in an async database request (slightly simplified):

  1. Request is accepted by IIS and passed to ASP.NET.
  2. ASP.NET takes one of its thread pool threads and assigns it to that request.
  3. WebApi creates DataController etc.
  4. The controller action starts an asynchronous SQL query.
  5. The request thread returns to the thread pool. There are now no threads processing the request.
  6. When the result arrives from the SQL server, a thread pool thread reads the response.
  7. That thread pool thread notifies the request that it is ready to continue processing.
  8. Since ASP.NET knows that no other threads are handling that request, it just assigns that same thread the request so it can finish it off directly.

If you want some proof-of-concept code, I have an old Gist that artificially restricts the ASP.NET thread pool to the number of cores (which is its minimum setting) and then does N+1 synchronous and asynchronous requests. That code just does a delay for a second instead of contacting a SQL server, but the general principle is the same.