ASP.NET Controller: An asynchronous module or handler completed while an asynchronous operation was still pending

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:

  1. If we remove task = DownloadAsync(); from the constructor and put it into the Index method it will work fine without the errors.
  2. If we use another DownloadAsync() body return 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?


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.


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 to null, call your async method, and then restore the original value.