我试图为使用异步编程来处理图像的HttpModule添加速度提升。

虽然看起来确实可以改善性能,但我想检查自己是否使用了正确提供的工具。

我特别担心我处理队列不正确。

我正在采用的方法。

  • 初始化ConcurrentQueue
  • 将ProcessImage方法添加到队列
    AddOnBeginRequestAsync
  • 中的BeginEventHandler
  • 处理EndEventHandler中的队列
    AddOnBeginRequestAsync

  • 有很多代码,所以我提前道歉,但是异步编程很困难:

    字段
    /// <summary>
    /// The thread safe fifo queue.
    /// </summary>
    private static ConcurrentQueue<Action> imageOperations;
    
    /// <summary>
    /// A value indicating whether the application has started.
    /// </summary>
    private static bool hasAppStarted = false;
    

    httpmodule初始化
    /// <summary>
    /// Initializes a module and prepares it to handle requests.
    /// </summary>
    /// <param name="context">
    /// An <see cref="T:System.Web.HttpApplication"/> that provides
    /// access to the methods, properties, and events common to all
    /// application objects within an ASP.NET application
    /// </param>
    public void Init(HttpApplication context)
    {
        if (!hasAppStarted)
        {
            lock (SyncRoot)
            {
                if (!hasAppStarted)
                {
                    imageOperations = new ConcurrentQueue<Action>();
                    DiskCache.CreateCacheDirectories();
                    hasAppStarted = true;
                }
            }
        }
    
        context.AddOnBeginRequestAsync(OnBeginAsync, OnEndAsync);
        context.PreSendRequestHeaders += this.ContextPreSendRequestHeaders;
    
    }
    

    事件处理程序
    /// <summary>
    /// The <see cref="T:System.Web.BeginEventHandler"/>  that starts
    /// asynchronous processing
    /// of the <see cref="T:System.Web.HttpApplication.BeginRequest"/>.
    /// </summary>
    /// <param name="sender">The source of the event.</param>
    /// <param name="e">
    /// An <see cref="T:System.EventArgs">EventArgs</see> that contains
    /// the event data.
    /// </param>
    /// <param name="cb">
    /// The delegate to call when the asynchronous method call is complete.
    /// If cb is null, the delegate is not called.
    /// </param>
    /// <param name="extraData">
    /// Any additional data needed to process the request.
    /// </param>
    /// <returns></returns>
    IAsyncResult OnBeginAsync(
    object sender, EventArgs e, AsyncCallback cb, object extraData)
    {
        HttpContext context = ((HttpApplication)sender).Context;
        EnqueueDelegate enqueueDelegate = new EnqueueDelegate(Enqueue);
    
        return enqueueDelegate.BeginInvoke(context, cb, extraData);
    
    }
    
    /// <summary>
    /// The method that handles asynchronous events such as application events.
    /// </summary>
    /// <param name="result">
    /// The <see cref="T:System.IAsyncResult"/> that is the result of the
    /// <see cref="T:System.Web.BeginEventHandler"/> operation.
    /// </param>
    public void OnEndAsync(IAsyncResult result)
    {
        // An action to consume the ConcurrentQueue.
        Action action = () =>
        {
            Action op;
    
            while (imageOperations.TryDequeue(out op))
            {
                op();
            }
        };
    
        // Start 4 concurrent consuming actions.
        Parallel.Invoke(action, action, action, action);
    }
    

    委托(delegate)并处理
    /// <summary>
    /// The delegate void representing the Enqueue method.
    /// </summary>
    /// <param name="context">
    /// the <see cref="T:System.Web.HttpContext">HttpContext</see> object that
    /// provides references to the intrinsic server objects
    /// </param>
    private delegate void EnqueueDelegate(HttpContext context);
    
    /// <summary>
    /// Adds the method to the queue.
    /// </summary>
    /// <param name="context">
    /// the <see cref="T:System.Web.HttpContext">HttpContext</see> object that
    /// provides references to the intrinsic server objects
    /// </param>
    private void Enqueue(HttpContext context)
    {
        imageOperations.Enqueue(() => ProcessImage(context));
    }
    

    最佳答案

    看来您的ProcessImage方法可在HttpContext上使用,它将是每次调用HttpModule的单个实例。每个Web请求都会根据需要调用HttpModule的OnBeginAsync,并且您的委托(delegate)已经在向您提供执行异步操作的逻辑。这意味着,您不需要4个并发线程,因为无论如何您只需要处理一个context实例。而且我们不需要ConcurrentQueue,因为有关context的所有工作都应在请求-响应的生命周期内完成。

    综上所述,您不需要ConcurrentQueue,因为:

    通过HttpModule的

  • 请求已经是并发的(来自Web主机体系结构)。
  • 每个请求都在单个context实例上工作。
  • ProcessImage返回之前,需要先完成context上的工作,然后才能完成OnEndAsync上的工作。

  • 相反,您只想在ProcessImage方法中开始OnBeginAsync的后台工作,然后确保在OnEndAsync方法中完成工作。另外,由于所有更改都是直接在context实例上进行的(我假设,由于ProcessImage没有返回类型,因此它正在更新context),因此您无需做任何进一步的工作来获取结果处理对象。

    您可以抛弃ConcurrentQueue并简单地使用:
    IAsyncResult OnBeginAsync(object sender, EventArgs e,
                              AsyncCallback cb, object extraData)
    {
        HttpContext context = ((HttpApplication)sender).Context;
        EnqueueDelegate enqueueDelegate = new EnqueueDelegate(ProcessImage);
    
        return enqueueDelegate.BeginInvoke(context, cb, extraData);
    }
    
    public void OnEndAsync(IAsyncResult result)
    {
        // Ensure our ProcessImage has completed in the background.
        while (!result.IsComplete)
        {
            System.Threading.Thread.Sleep(1);
        }
    }
    

    您可以删除ConcurrentQueue<Action> imageOperationsEnqueue,也可以将EnqueueDelegate重命名为ProcessImageDelegate,因为现在它可以直接与该方法一起使用。

    注意:可能是在context时您的ProcessImage尚未准备好使用OnBeginAsync。如果是这种情况,您将必须将ProcessImage作为简单的同步调用移动到OnEndAsync中。但是,也就是说,确实有可能通过某种并发来改善ProcessImage

    我要指出的另一点是,可以将hasAppStarted重命名为hasModuleInitialized,以减少歧义。

    08-17 23:37