Reputation: 12468
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?
Upvotes: 6
Views: 4497
Reputation: 964
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?
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
without await
, 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 the Stop
method of your implementation of the IRegisteredObject
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.
Upvotes: 0
Reputation: 61744
I need to implement an asynch Action in my Controller for a long running external API call.
...
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:
[AsyncTimeout(200)]
public async Task<ActionResult> DoAsync()
{
// Execute long running call.
return View();
}
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 the View
as without Task.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.
Upvotes: 9
Reputation: 19505
As the other person wrote you need an asynchronous action to await it, to make View()
async wrap it in a Task.Run
[AsyncTimeout(200)]
public async Task<ActionResult> DoAsync()
{
// Execute long running call.
return await Task.Run(() => View());
}
Afterwards this method is mainly useful for calling from other async functions or callbacks.
Upvotes: 0
Reputation: 888273
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.
Upvotes: 9