【问题标题】:Async EventHandlers in a HttpModuleHttpModule 中的异步事件处理程序
【发布时间】:2014-08-17 10:29:23
【问题描述】:

您如何设置它们?

如果我在 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 之类的东西对图像进行后处理以进一步优化它。最好的方法是什么?

【问题讨论】:

  • This 可能会有所帮助
  • 如果调用线程只是要等待它完成,我不明白启动一个新线程的意义?
  • 该代码仅用于测试/演示以查看它是否正在触发。实际消耗方法会很耗时

标签: c# events asynchronous httpmodule


【解决方案1】:

您可以使用HttpApplication 类中的AddOn* 方法添加异步事件处理程序。我确定并非所有人都支持 async void 方法。也许他们都没有。

要使用这些方法,尽管它们不直接支持任务,you need to adapt your task to be compatible with the APM pattern which ASP.NET uses here

也许这只是示例代码,但您使用 Task.Factory.StartNew 在 Web 应用程序的上下文中没有帮助。

【讨论】:

  • 好的,如果我使用context.AddOnPostRequestHandlerExecuteAsync,我将如何确保可以将正确的缓存路径传递给该任务?将其存储在 context.Items 中?是的,这只是一些快速的示例代码。 Task.Run(() =&gt; Debug.WriteLine(e.CachedPath)) 可以吗?
  • 我对你的这个缓存路径了解不多。我不知道该建议什么以及问题是什么。 Task.Run 也有同样的问题:它对您没有帮助并增加了开销。
【解决方案2】:

关键是你需要避免async void。有几个地方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 on my blog),但我更喜欢 WinStore 应用程序使用的“延迟”方法,因此开发人员可能更熟悉它。

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

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);
  }
}

最后一点,StartNewRun 在 ASP.NET 上都不是一个好主意。如果你有同步代码要运行,直接运行即可。

【讨论】:

  • 所以最后你指出,当完美的调用线程无所事事等待“工作线程”时,启动一个新线程进行一些处理是完全浪费处理时间的" 结束
  • @Mick:我假设StartNew(() =&gt; Debug.WriteLine(...)) 是真正异步工作的示例占位符。
  • 这是一个占位符,只是为了测试事件是否正在触发。不过,这确实提出了另一个问题。如何在不使用Task.Run 的情况下运行同步耗时的作业?假设我想枚举一系列目录来清理文件。如何在不导致请求排队和线程池增长的情况下进行管理?
  • 我试图提出的观点与工作线程中正在完成的工作量无关。产生新线程的成本很高,创建它们需要时间,也许我误解了代码,但看起来你有一个由 HttpModule 管理的线程 A,你正在从线程 A 创建线程 B 来做一些工作,然后停止线程 A 直到线程 B 终止。线程 B 的意义何在?为什么不在线程 A 中进行所有处理?太好了,您正在使用并行库,但没有并行执行。
  • @JamesSouth:同步耗时的作业应该直接运行。请注意,您的示例(枚举一系列目录以清理文件)在技术上是异步的而不是同步的,但遗憾的是 .NET BCL 不提供这些方法的异步版本。
【解决方案3】:

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

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

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

所以有一个在模块期间运行的工作线程,并让请求简单地为工作线程添加工作来处理

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-09-06
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多