I need to implement an asynch Action in my Controller for a long running external API call.
Looking at some tutorials, I have implemented my method like this:
[AsyncTimeout(200)]
public async Task<ActionResult> DoAsync()
{
// Execute long running call.
return View();
}
My question is, is this enough to make is truly non-block asynchronous? Do I also need to apply the await operator, and if so how should I do that?
The C# compiler is probably already suggesting the
async
keyword is redundant here:The fact that you've added the
async
keyword doesn't make your method magically run in the background.If you do something like
await Task.Run(() => View())
as suggested by another answer, you still won't break through the boundaries of a given HTTP request. The request processing will take at least as much time to generate theView
as withoutTask.Run
. The client-side browser will still be waiting for it.This pattern is good for a UI app, when you need to offload a CPU-bound work to a pool thread, to avoid blocking the UI thread and keep the UI responsive. However, using it within an HTTP request handler inside an ASP.NET app is almost never a good idea. It will only hurt the performance and scalability.
One solution, providing a user-friendly experience for when the
View
takes significant amount of time to compose, is to run a background task which spans the boundary of a single HTTP requests. Then further use AJAX requests to keep the client-side browser updated with the progress. Here's a great example by Alan D. Jackson, doing just that:Long Running Background Tasks in Asp.Net MVC3.
However, running a lengthy background operation across multiple HTTP requests inside the same ASP.NET server process is not a very good idea. While it's relatively easy to implement, this approach may create issues with IIS maintainability, scalability and security.
You might be better off with a separate Windows/WCF service for that, which would expose a
Task
-based API. Then use AJAX to periodically poll the WCF service, using a dedicated method of your ASP.NET MVC controller as a proxy for the polling call.To write non-blocking asynchronous code, you need to execute some kind of existing non-blocking asynchronous operation, such as
Task.Delay()
, or async network or file IO.In short, the
await
keyword consumes asynchrony; it does not create it.If you don't have any actual asynchronous work to do,
await
won't do you any good.You should use the
await
keyword in asynchronous MVC actions for the following reasons:await
keyword gives you a possibility to rollback changes in the case of external service failure. Otherwise this may lead to inconsistent state, because user can't replay the given async operation only, it can replay only the whole action.That is why you should not use neither
async
withoutawait
, nor other .NET async techniques like TPL. In this case, a custom Windows service with WCF is a preferable solution, but it greatly complicates the programming, deployment and maintenance tasks.Of course, you can use the
HostingEnvironment.RegisterObject
, here is a good article. But when ASP.NET call theStop
method of your implementation of theIRegisteredObject
interface (during the shutdown), you have only 30 seconds (by default) to save your data. If you have outstanding async operations, you should abort them, mark them failed and retry them after restart or send failure notifications to users. And if your storage will also be unavailable at this time, then say bye-bye to your results (including failed results).You can also use persistent queues, reliable fetching and dedicated workers that listen these queues inside ASP.NET application. This can protect you even from worker process termination. Moreover, there are a lot of project, like Resque, Sidekiq, Pyres, etc., but they are for other languages.
For .NET, give a try to HangFire – it is under development yet, but much stable and than initial implementation of such systems, and has many different features.
As the other person wrote you need an asynchronous action to await it, to make
View()
async wrap it in aTask.Run
Afterwards this method is mainly useful for calling from other async functions or callbacks.