Async EventHandlers in HttpModule
How do you set them up?
If I have the following code in HttpModule.
public static event EventHandler<PostProcessingEventArgs> OnPostProcessing;
And in the async PostAuthorizeRequest task configured with EventHandlerTaskAsyncHelper
.
// Fire the post processing event.
EventHandler<PostProcessingEventArgs> handler = OnPostProcessing;
if (handler != null)
{
handler(this, new PostProcessingEventArgs { CachedPath = cachedPath });
}
And then touch it using that.
ProcessingModule.OnPostProcessing += this.WritePath;
private async void WritePath(object sender, PostProcessingEventArgs e)
{
await Task.Factory.StartNew(() => Debug.WriteLine(e.CachedPath));
}
I am getting the following error.
An asynchronous module or handler completed while asynchronous work has not yet completed.
Edit
Ok, so before I saw all these answers, I got it to not throw an error by raising an event handler like this.
EventHandlerTaskAsyncHelper postProcessHelper =
new EventHandlerTaskAsyncHelper(this.PostProcessImage);
context.AddOnPostRequestHandlerExecuteAsync(postProcessHelper.BeginEventHandler,
postProcessHelper.EndEventHandler);
private Task PostProcessImage(object sender, EventArgs e)
{
HttpContext context = ((HttpApplication)sender).Context;
object cachedPathObject = context.Items[CachedPathKey];
if (cachedPathObject != null)
{
string cachedPath = cachedPathObject.ToString();
// Fire the post processing event.
EventHandler<PostProcessingEventArgs> handler = OnPostProcessing;
if (handler != null)
{
context.Items[CachedPathKey] = null;
return Task.Run(() => handler(this,
new PostProcessingEventArgs { CachedImagePath = cachedPath }));
}
}
return Task.FromResult<object>(null);
}
From what I see below, it seems unreasonable though.
The sole purpose of this event handler would be to allow someone to do longer running tasks in the file, such as using something like jpegtran or pngout to post-process the image for further optimization. What's the best approach for this?
source to share
You can add async event handlers using methods AddOn*
in the class HttpApplication
. I'm pretty sure async methods are not supported by all of them. Maybe none of them.
In order to use these methods, even though they do not directly support tasks, you need to adapt your task to be compatible with the APM pattern that ASP.NET uses here .
Maybe this is just a sample code, but the one you are using is Task.Factory.StartNew
not useful in the context of a web application.
source to share
It complains that the worker thread did not complete before the request thread finished. This is not the case ... for anyone who knows that your working thread cannot run out, which will lead to thread starvation in a very short amount of time.
If you want to have a worker thread, you need to create it in the Init of your HttpModule. I think this is a pretty good example ...
http://msdn.microsoft.com/en-us/library/hh567803(v=cs.95).aspx
So you have one worker thread running throughout the entire module, and let the requests just add work for the worker thread to handle
source to share
The key is that you need to avoid async void
. There are several places where it async void
can touch you.
You are already handling the first one correctly using EventHandlerTaskAsyncHelper
. I am assuming your setup code looks something like this:
public void Init(HttpApplication context)
{
var helper = new EventHandlerTaskAsyncHelper(InvokePostAuthEvents);
context.AddOnPostAuthorizeRequestAsync(helper.BeginEventHandler,
helper.EndEventHandler);
}
With this setup, you avoid async void PostAuthorizeRequest
.
The other side is when you raise the event OnPostProcessing
. This is where the problems arise with async void
. There are many ways to raise async
-aware events (I share a number of them on my blog ), but I prefer the "deferred" method that WinStore applications use, so it will probably be more familiar to developers.
I have DeferralManager
in my AsyncEx library that is meant to be used in your events like so:
public class PostProcessingEventArgs
{
private readonly DeferralManager _deferrals;
public PostProcessingEventArgs(DeferralManager deferrals, ...)
{
_deferrals = deferrals;
...
}
public IDisposable GetDeferral()
{
return deferrals.GetDeferral();
}
...
}
When you raise an event, you do this:
Task RaisePostProcessingEventAsync()
{
EventHandler<PostProcessingEventArgs> handler = OnPostProcessing;
if (handler == null)
return TaskConstants.Completed;
var deferrals = new DeferralManager();
var args = new PostProcessingEventArgs(deferrals) { CachedPath = cachedPath };
handler(this, args);
return deferrals.SignalAndWaitAsync();
}
Note that event generation is now asynchronous, as it (asynchronously) waits for all event handler deferrals to complete.
Regular (synchronous) event handlers do not need to be changed, but asynchronous event handlers should use deferral, for example:
private async void WritePath(object sender, PostProcessingEventArgs e)
{
using (e.GetDeferral())
{
await Task.Delay(1000);
Debug.WriteLine(e.CachedPath);
}
}
As a final note, none of StartNew
and Run
is not a good idea to ASP.NET. If you have synchronous code to run, just run it directly.
source to share