C# – Error: An asynchronous module or handler completed while an asynchronous operation was still pending

asp.net-mvcasp.net-mvc-3asp.net-mvc-4c

I have a Controller Action method to Save user Details like below.

public async Task<ActionResult> SaveUser(ViewModel.VM_CreateUser user)
{
    var result = await _user.Save(userDetails);
    return Json(new { Success = String.IsNullOrEmpty(result) });
}

So far there is no issue in above 4 lines function.

public async Task<ActionResult> SaveUser(ViewModel.VM_CreateUser user)
{
    var result = await _user.Save(userDetails);
    new MailController().CreateUser(user.userDetails); //<==This is problem line of code.
}

and Below is my Mail Controller.

public class MailController : MailerBase
{
    public void CreateUser(ViewModel.VM_User user)
    {
        To.Add(user.EmailAddress);
        From = System.Configuration.ConfigurationManager.AppSettings["emailSender"];
        Subject = "Hi";
        Email("CreateUser", user).DeliverAsync();
    }
}

in the above code, I am facing problem when I execute the Email Sending code. I get below error message.

An asynchronous module or handler completed while an asynchronous
operation was still pending

Please suggest the corrective action !!

Best Answer

This is because async methods track their completion, even async void. They do this by registering with the SynchronizationContext when starting and marking the operation complete when returning. ASP.NET tracks all created operations and requires them to all be completed before your action returns, otherwise it returns an error to the HTTP client. If you need to run a “fire and forget” method, you must manually avoid launching it on the ASP.NET SynchronizationContext. For example, await Task.Run(() => CallFireAndForget()) (await so that you wait for the synchronous portion to run on the thread pool. This works because CallFireAndForget() is async void. To fire a method which returns a Task as fire-and-forget, you have to explicitly avoid returning the Task to Task.Run() like this: await Task.Run(() => { CallFireAndForgetAsync(); })).

Another way to get the same error message to show up should be to write this in an asynchronous action:

// BAD CODE, DEMONSTRATION ONLY!
AsyncOperationManager.CreateOperation();

If you use the AsyncOperation API, you have to ensure you call AsyncOperation.OperationCompleted() prior to allowing the action method to return.

In your case, if the mail sending is really intended to be a fire-and-forget task, you can do the following inside of CreateUser after changing its signature to async Task CreateUserAsync(ViewModel.VM_User user):

await Task.Run(() => Email("CreateUser", user).DeliverAsync());

and in your action:

await new MailController().CreateUserAsync(user.userDetails);

Ideally, you wouldn’t use Task.Run() for something like this. Instead, you can temporarily replace the current SynchronizationContext instead (NOTE WELL: Never await inside the try{}—instead, if you need to, store the Task in a local and await it after restoring the original SynchronizationContext (it may be safer to use a primitive such as SynchronizationContextSwitcher.NoContext() (source) which does the right thing)):

var originalSynchronizationContext = SynchronizationContext.Current;
try
{
    SynchronizationContext.SetSynchronizationContext(null);
    new MailController().CreateUser(user.userDetails);
}
finally
{
    SynchronizationContext.SetSynchronizationContext(originalSynchronizationContext);
}