一个 HttpModule 中的异步事件处理程序

6
你如何设置它们?
如果我在HttpModule中有以下代码。
public static event EventHandler<PostProcessingEventArgs> OnPostProcessing;

在使用EventHandlerTaskAsyncHelper设置异步PostAuthorizeRequest任务时。
// Fire the post processing event.
EventHandler<PostProcessingEventArgs> handler = OnPostProcessing;
if (handler != null)
{
    handler(this, new PostProcessingEventArgs { CachedPath = cachedPath });
}

然后使用以下方式进行访问。
ProcessingModule.OnPostProcessing += this.WritePath;    

private async void WritePath(object sender, PostProcessingEventArgs e)
{
    await Task.Factory.StartNew(() => Debug.WriteLine(e.CachedPath));
}

我得到了以下错误。
异步模块或处理程序在仍有异步操作挂起时完成。
编辑
好的,在看到所有这些答案之前,我通过如下方式触发事件处理程序来避免抛出错误。
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);
}

从我下面所看到的来看,这似乎是不明智的。

这个事件处理程序的单一目的是允许某人在文件上运行更长时间的任务,例如使用像jpegtran或pngout之类的东西对图像进行后处理以进一步优化它。 最好的方法是什么?


这可能会有所帮助:https://dev59.com/i3TYa4cB1Zd3GeqPz-b3 - Yuval Itzchakov
如果调用线程只是等待新线程完成,那么开启新线程的意义何在? - Mick
这段代码只是用于测试/演示是否触发。实际的消耗方法会很耗时间。 - James South
3个回答

4

如果我使用了 context.AddOnPostRequestHandlerExecuteAsync,我该如何确保能将正确的缓存路径传递给该任务?将其存储在 context.Items 中?是的,那只是一些快速的示例代码。Task.Run(() => Debug.WriteLine(e.CachedPath)) 是可以的,对吧? - James South
我不太了解你的CachedPath。我不知道该建议什么以及问题出在哪里。Task.Run也存在同样的问题:它对你没有帮助,反而增加了开销。 - usr

1

关键是要避免使用async void。有几个地方可能会让你陷入困境。

你已经通过使用EventHandlerTaskAsyncHelper来正确处理第一个问题了。我假设你的设置代码类似于以下内容:

public void Init(HttpApplication context)
{
  var helper = new EventHandlerTaskAsyncHelper(InvokePostAuthEvents);
  context.AddOnPostAuthorizeRequestAsync(helper.BeginEventHandler,
      helper.EndEventHandler);
}

通过这种设置,您可以避免使用 async void PostAuthorizeRequest

另一方面,当您 引发 OnPostProcessing 事件时,会出现 async void 的问题。有多种方法可以引发 async -aware 事件(我在我的博客上介绍了其中的 number of them),但我更喜欢使用“延迟”方法,这是 WinStore 应用程序使用的方法,因此对开发人员来说可能更加熟悉。

我在我的 AsyncEx 库中有一个 DeferralManager,旨在用于您的事件参数,如下所示:

public class PostProcessingEventArgs
{
  private readonly DeferralManager _deferrals;

  public PostProcessingEventArgs(DeferralManager deferrals, ...)
  {
    _deferrals = deferrals;
    ...
  }

  public IDisposable GetDeferral()
  {
    return deferrals.GetDeferral();
  }

  ...
}

当你触发事件时,你会这样做:
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();
}

请注意,现在触发事件是一个异步操作,因为它会(异步地)等待所有事件处理程序的延迟完成。
常规(同步)事件处理程序不需要更改,但异步事件处理程序需要使用延迟,如下所示:
private async void WritePath(object sender, PostProcessingEventArgs e)
{
  using (e.GetDeferral())
  {
    await Task.Delay(1000);
    Debug.WriteLine(e.CachedPath);
  }
}

作为最后一点说明,无论是 StartNew 还是 Run 都不是在 ASP.NET 上一个好的选择。如果您有同步代码需要运行,只需直接运行即可。

因此,最后你指出,启动一个新线程来进行一些处理是完全浪费处理时间的,因为完全可以使用空闲的调用线程等待“工作线程”完成。 - Mick
@Mick:我以为 StartNew(() => Debug.WriteLine(...)) 只是一个真正异步工作的例子占位符。 - Stephen Cleary
那只是一个占位符,用来测试事件是否触发。这确实引出了另一个问题。如果不使用Task.Run,如何运行同步耗时的作业?比如我想枚举一系列目录以清理文件,如何管理它而不会导致请求排队和线程池增长? - James South
@Mick:我所说的是真正的异步操作,它们不使用线程 - Stephen Cleary
1
@JamesSouth: 同步的耗时任务应该直接运行。请注意,您的示例(枚举一系列目录以清理文件)在技术上是异步而不是同步的,但不幸的是,.NET BCL没有提供这些方法的异步版本。 - Stephen Cleary
显示剩余8条评论

0

它在抱怨工作线程在请求线程终止之前没有完成。这是不允许的...因为它不知道你的工作线程可能永远不会终止,这将导致线程饥饿,在很短的时间内。

如果你想要一个工作线程,你需要在HttpModule的Init中创建它。我认为这是一个相当好的例子...

http://msdn.microsoft.com/en-us/library/hh567803(v=cs.95).aspx

因此,有一个单个的工作线程,在模块的整个生命周期内运行,并让请求简单地添加工作,以供工作线程处理。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接