ASP.NET Controller: an Asynchronous Module or Handler Completed While an Asynchronous Operation Was Still Pending

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

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.

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

Finally, I resolved the above exception when I am return Task instead of void in DeliveryProgressReport method. and also where ever I was called the await DeliveryProgressReport() method there also I return Task instead of void.

-Pradeep

I use await keyword but still get this error, why? An asynchronous module or handler completed while an asynchronous operation was still pending

As I note in my article on async ASP.NET, this error is usually due to an async void method. With async void, there is no Task returned, so there's no way for your code to await its completion.

In your particular case (OnAuthorization), unfortunately there isn't a way to do this asynchronously. Also in my async ASP.NET article, I describe how there are two limitations of async support in classic ASP.NET: action filters and child actions. These two cannot safely be made async in classic ASP.NET. (ASP.NET Core does allow async filters and view components). In this case, OnAuthorization is an action filter, and hence cannot be async.

The only solutions are to either upgrade to ASP.NET Core and use an async action filter, or make your filter synchronous. I recommend making all of the code synchronous, but it is possible to use some form of sync-over-async as well.

Send email async: An asynchronous module or handler completed while an asynchronous operation was still pending

Since the runtime can recycle your appdomain when it knows there are no more pending requests, starting tasks that you don't wait for is specifically not recommended, hence that message.

In particular, without this check you would have no guarantee this email would ever be sent since the entire appdomain could be yanked down around that task after your controller action returns.

Now in your case you might say that the loss of the email is acceptable, but the runtime doesn't know that it is only an email, it could very well be your database call that you've forgotten to wait for that would be prematurely aborted. Hence it just informs you that this is bad form.

Instead you got two options:

  1. Wait for that task to complete, which you have specifically said you don't want to do
  2. Inform the runtime that you're firing off a task that you specifically don't want to wait for, but please oh please, don't yank out the appdomain before task has completed

This last part is what HostingEnvironment.QueueBackgroundWorkItem is for.

In your case you would call it like this:

HostingEnvironment.QueueBackgroundWorkItem(ct =>
_mailService.SendAccountConfirmationEmailAsync(user));

Additionally, the method provides a CancellationToken, you should check if there is an overload of SendAccountConfirmationEmailAsync that accepts it. If not, and if this is your API, you should consider adding support for such tokens.

Please be aware that this mechanism is not meant for long-running threads or tasks, for that there are other more appropriate trees to bark up, but in this case it should be enough.

Asynchronous module completed while another operation is pending

This kind of error is commonly caused by async void. And I see one right here:

qbEmpsList.ForEach(async e =>
{
...
});

You'd probably want to make this into a regular foreach:

foreach (var e in qbEmpsList)
{
...
}

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

I ended up reworking the way my Async email is sent with the following:

public void SendAsyncEmail(MailMessage message)
{

var client = new SmtpClient("mail.hover.com", 587)
{
Credentials = new NetworkCredential("admin@site.com", "Covert00!"),
EnableSsl = false
};
client.SendCompleted += (sender, error) =>
{
if (error.Error != null)
{
// TODO: get this working
throw new WarningException("Email Failed to send.");
}
client.Dispose();
message.Dispose();
};
ThreadPool.QueueUserWorkItem(o => client.SendAsync(message, Tuple.Create(client, message)));
}

Note that this is a temporary solution, and that the correct way (it seems) to handle emailing is to use some kind of queue service paired with a worker role or other windows service to pop the messages.



Related Topics



Leave a reply



Submit