I have a standard, non-async action like:
[HttpPost]
public JsonResult StartGeneratePdf(int id)
{
PdfGenerator.Current.GenerateAsync(id);
return Json(null);
}
The idea being that I know this PDF generation could take a long time, so I just start the task and return, not caring about the result of the async operation.
In a default ASP.Net MVC 4 app this gives me this nice exception:
System.InvalidOperationException: An asynchronous operation cannot be started at this time. Asynchronous operations may only be started within an asynchronous handler or module or during certain events in the Page lifecycle. If this exception occurred while executing a Page, ensure that the Page is marked <%@ Page Async="true" %>.
Which is all kinds of irrelevant to my scenario. Looking into it I can set a flag to false to prevent this Exception:
<appSettings>
<!-- Allows throwaway async operations from MVC Controller actions -->
<add key="aspnet:AllowAsyncDuringSyncStages" value="true" />
</appSettings>
https://stackoverflow.com/a/15230973/176877
http://msdn.microsoft.com/en-us/library/hh975440.aspx
But the question is, is there any harm by kicking off this Async operation and forgetting about it from a synchronous MVC Controller Action? Everything I can find recommends making the Controller Async, but that isn't what I'm looking for - there would be no point since it should always return immediately.
Relax, as Microsoft itself says (http://msdn.microsoft.com/en-us/library/system.web.httpcontext.allowasyncduringsyncstages.aspx):
Just remember a few simple rules:
Never await inside (async or not) void events (as they return immediately). Some WebForms Page events support simple awaits inside them - but
RegisterAsyncTaskis still the highly preferred approach.Don't await on async void methods (as they return immediately).
Don't wait synchronously in the GUI or Request thread (
.Wait(),.Result(),.WaitAll(),WaitAny()) on async methods that don't have.ConfigureAwait(false)on root await inside them, or their rootTaskis not started with.Run(), or don't have theTaskScheduler.Defaultexplicitly specified (as the GUI or Request will thus deadlock).Use
.ConfigureAwait(false)orTask.Runor explicitly specifyTaskScheduler.Defaultfor every background process, and in every library method, that does not need to continue on the synchronization context - think of it as the "calling thread", but know that it is not one (and not always on the same one), and may not even exist anymore (if the Request already ended). This alone avoids most common async/await errors, and also increases performance as well.Microsoft just assumed you forgot to wait on your task...
UPDATE: As Stephen clearly (pun not intended) stated in his answer, there is an inherit but hidden danger with all forms of fire-and-forget when working with application pools, not solely specific to just async/await, but Tasks, ThreadPool, and all other such methods as well - they are not guaranteed to finish once the request ends (app pool may recycle at any time for a number of reasons).
You may care about that or not (if it's not business-critical as in the OP's particular case), but you should always be aware of it.