ASP.NET Controller: An asynchronous module or handler completed while an asynchronous operation was still pending
Solution 1
In Async Void, ASP.Net, and Count of Outstanding Operations, Stephan Cleary explains the root of this error:
Historically, ASP.NET has supported clean asynchronous operations since .NET 2.0 via the Event-based Asynchronous Pattern (EAP), in which asynchronous components notify the SynchronizationContext of their starting and completing.
What is happening is that you're firing DownloadAsync
inside your class constructor, where inside you await
on the async http call. This registers the asynchronous operation with the ASP.NET SynchronizationContext
. When your HomeController
returns, it sees that it has a pending asynchronous operation which has yet to complete, and that is why it raises an exception.
If we remove task = DownloadAsync(); from the constructor and put it into the Index method it will work fine without the errors.
As I explained above, that's because you no longer have a pending asynchronous operation going on while returning from the controller.
If we use another DownloadAsync() body return await
Task.Factory.StartNew(() => { Thread.Sleep(3000); return "Give me an error"; });
it will work properly.
That's because Task.Factory.StartNew
does something dangerous in ASP.NET. It doesn't register the tasks execution with ASP.NET. This can lead to edge cases where a pool recycle executes, ignoring your background task completely, causing an abnormal abort. That is why you have to use a mechanism which registers the task, such as HostingEnvironment.QueueBackgroundWorkItem
.
That's why it isn't possible to do what you're doing, the way you're doing it. If you really want this to execute in a background thread, in a "fire-and-forget" style, use either HostingEnvironment
(if you're on .NET 4.5.2) or BackgroundTaskManager
. Note that by doing this, you're using a threadpool thread to do async IO operations, which is redundant and exactly what async IO with async-await
attempts to overcome.
Solution 2
ASP.NET considers it illegal to start an “asynchronous operation” bound to its SynchronizationContext
and return an ActionResult
prior to all started operations completing. All async
methods register themselves as “asynchronous operation”s, so you must ensure that all such calls which bind to the ASP.NET SynchronizationContext
complete prior to returning an ActionResult
.
In your code, you return without ensuring that DownloadAsync()
has run to completion. However, you save the result to the task
member, so ensuring that this is complete is very easy. Simply put await task
in all of your action methods (after asyncifying them) prior to returning:
public async Task<ActionResult> IndexAsync()
{
try
{
return View();
}
finally
{
await task;
}
}
EDIT:
In some cases, you may need to call an async
method which should not complete prior to returning to ASP.NET. For example, you may want to lazily initialize a background service task which should continue running after the current request completes. This is not the case for the OP’s code because the OP wants the task to complete before returning. However, if you do need to start and not wait for a task, there is a way to do this. You simply must use a technique to “escape” from the current SynchronizationContext.Current
.
(not recommenced) One feature of
Task.Run()
is to escape the current synchronization context. However, people recommend against using this in ASP.NET because ASP.NET’s threadpool is special. Also, even outside of ASP.NET, this approach results in an extra context switch.(recommended) A safe way to escape the current synchronization context without forcing an extra context switch or bothering ASP.NET’s threadpool immediately is to set
SynchronizationContext.Current
tonull
, call yourasync
method, and then restore the original value.
Solution 3
I ran into related issue. A client is using an Interface that returns Task and is implemented with async.
In Visual Studio 2015, the client method which is async and which does not use the await keyword when invoking the method receives no warning or error, the code compiles cleanly. A race condition is promoted to production.
Solution 4
Method return async Task
, and ConfigureAwait(false)
can be one of the solution. It will act like async void and not continue with the sync context (As long as you really don't concern the end result of the method)
Solution 5
I had this error today when building an API controller. It turned out that the solution was simple in my case.
I had:
public async void Post()
And I needed to change it to:
public async Task Post()
Note, the compiler did not warn about async void
.
Comments
-
dyatchenko about 4 years
I have a very simple ASP.NET MVC 4 controller:
public class HomeController : Controller { private const string MY_URL = "http://smthing"; private readonly Task<string> task; public HomeController() { task = DownloadAsync(); } public ActionResult Index() { return View(); } private async Task<string> DownloadAsync() { using (WebClient myWebClient = new WebClient()) return await myWebClient.DownloadStringTaskAsync(MY_URL) .ConfigureAwait(false); } }
When I start the project I see my view and it looks fine, but when I update the page I get the following error:
[InvalidOperationException: An asynchronous module or handler completed while an asynchronous operation was still pending.]
Why does it happen? I made a couple of tests:
- If we remove
task = DownloadAsync();
from the constructor and put it into theIndex
method it will work fine without the errors. - If we use another
DownloadAsync()
bodyreturn await Task.Factory.StartNew(() => { Thread.Sleep(3000); return "Give me an error"; });
it will work properly.
Why it is impossible to use the
WebClient.DownloadStringTaskAsync
method inside a constructor of the controller? - If we remove
-
dyatchenko about 9 yearsThanks for your answer. I've already read the article that you mentioned, but I read it again. I don't know why I didn't realize it before, but the key to my question is supposed to be the second phrase: "The MVC framework understands how to wait for your
Task
but it doesn't even know about theasync void
, so it returns completion to the ASP.NET core which sees that it's not actually complete." The controller constructors is compiled into thevoid
methods and that is why I'm getting the error. Am I right? -
Yuval Itzchakov about 9 years@dyatchenko No. This has nothing to do with
async void
. ASP.NET knows how to handleasync Task
. Because it registers to theSynchronizationContext
, it notices that yourDownloadAsync
method isn't complete when your controller returns. -
dyatchenko about 9 yearsOk. In this case, as you mentioned, I can wait a couple of seconds, while my download is completed, and then I can refresh the page and it supposed to be fine, but it isn't.
-
Yuval Itzchakov about 9 yearsI'm not even sure what that means. You can either execute this code synchronously, or execute it on a threadpool thread using the appropriate measures as i mentioned in the answer. But you can't execute it asynchronously the way you're doing it currently.
-
dyatchenko about 9 yearsBut, I'm getting the result from the
DownloadStringTaskAsync
method. I've just put a breakpoint in to check it up. Or it doesn't mean that I got the result "asynchronously"? -
Yuval Itzchakov about 9 years@dyatchenko You got a result, but your
Index()
method finished before the result arrived. That's why you see an exception. -
dyatchenko about 9 yearsIf I put
Thread.Sleep(10000);
into theIndex()
method it will not work. But when I made theIndex()
method asynchronous and waited for 10 seconds inside, it would work. Why a synchronous timeout doesn't help? -
binki over 6 years@dyatchenko A synchronous sleep does not help because the task which is registered on the ASP.NET
SynchronizationContext
needs to run on the thread that your synchronous sleep is blocking. If you do an asynchronous sleep, e.g.,await Task.Delay(TimeSpan.FromSeconds(10))
, other tasks will run on your current thread and have a chance to complete. However, waiting for time to pass is not a solution as your download may take longer. The correct solution is to putawait task
in all of your actions methods. -
Vivian River about 6 yearsThanks for this answer. I had the exact same problem, and yet this is a different way to cause the same problem the OP described.
-
O. R. Mapper over 5 years"that's because you no longer have a pending asynchronous operation going on while returning from the controller" - could you briefly elaborate on why that is, please? When
Index()
is called withtask = DownloadAsync();
inside, it seems to me that the asynchronous operation would just as well still be going on whenIndex()
finishes. -
Gargoyle almost 5 yearsHow does this relate to the question? You should be using
client.SendMailAsync(message)
there. Why are you doing an await on task yield?