I read an excellent article about asynchronous tasks inside a Web API method, see link:
http://blog.tonysneed.com/2013/03/22/async-services-asp-net-web-api-entity-framework-6
Now for my solution (Framework 4.0) I have a third party tool which can transform an URL into an image. I like to use this inside a Web API method. Example pseudo code:
[HttpPost]
public HttpResponseMessage SaveSomething(...)
{
SaveSomethingToDatabase(...);
var image = ThirdPartyTool(someUrl);
image.Save("someFullName");
}
Because the third party tool takes some time (3 to 6 seconds on average, the URL called loads a Google Maps layer) I like to know if I should embed this inside a Task (like the article describes, but then the third party tool should have some async support which it hasn't) or is Web API reliable enough to handle this kind of scenario's (regarding multiple repeated requests)?
A better way would be of course to create a windows service dedicated in creating images. But then I need to create a lot of extra stuff (a reliable image queue, a windows service an installer etc).
First off, ASP.NET 4.0 does not support async
and await
. Full stop. If you install Microsoft.Bcl.Async, you can get it to compile, but the behavior at runtime is undefined. End of story, sorry.
But even if it was possible (e.g., after upgrading to ASP.NET 4.5), you still wouldn't want to do this. You should absolutely not wrap this in a Task
.
Consider the current synchronous behavior:
ThirdPartyTool
method), the request thread is blocked and idle.This is not ideal, because you do have a request thread that is blocked not doing anything useful. However, ASP.NET is designed to handle this scenario gracefully, since it is (today) quite common.
Now, let's wrap it in a Task
. Since the third-party tool is fully synchronous, the only option we really have is Task.Run
or an equivalent:
[HttpPost]
public async Task<HttpResponseMessage> SaveSomething(...)
{
SaveSomethingToDatabase(...);
var image = await Task.Run(() => ThirdPartyTool(someUrl));
image.Save("someFullName");
}
Now, consider how this asynchronous request is handled:
A
from its thread pool and assigns it to the request.Task.Run
, it queues ThirdPartyTool
to the thread pool. This takes another thread B
from ASP.NET's thread pool and starts executing the code.await
s the task returned from Task.Run
. This frees up the request thread A
, allowing it to return to the ASP.NET thread pool.B
completes ThirdPartyTool
, it notifies the handler that its task is complete. The handler then executes Save
(as an optimization, the remainder of the handler is actually executed on thread B
instead of pulling another thread from the thread pool, but that's an implementation detail).If you think about it, it's definitely a performance pessimization. You're doing the same amount of work but also doing at least one extra context switch and messing with the ASP.NET thread pool heuristics twice by unexpectedly taking a task and then unexpectedly returning it later.
For this reason, it's a good guideline to avoid Task.Run
in ASP.NET.
Wrapping code in Task.Run
is what I call "fake asynchronous" - it acts asynchronous and looks asynchronous but underneath it's just queueing work to a thread pool thread.
Now, if you have a truly asynchronous operation, it's a different story. Let's say that the third party upgrades its tool with asynchronous support, and they do it right. Then your handler could look like this:
[HttpPost]
public async Task<HttpResponseMessage> SaveSomething(...)
{
SaveSomethingToDatabase(...);
var image = await ThirdPartyToolAsync(someUrl);
image.Save("someFullName");
}
And this time the asynchronous request is handled completely differently:
ThirdPartyToolAsync
starts an asynchronous web request to Google (or whatever).await
s the task returned from ThirdPartyToolAsync
. This frees up the request thread, allowing it to return to the ASP.NET thread pool.ThirdPartyToolAsync
and proceeds to finish SaveSomething
.It's only with truly asynchronous methods like this that you get any benefit from async
on ASP.NET, because you're reducing the pressure on the thread pool. Fake asynchronous methods increase the pressure, providing negative benefit.