在HttpModule中正确使用ConcurrentQueue?

时间:2013-03-20 17:25:49

标签: c# asp.net multithreading concurrency httpmodule

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

虽然看起来我的性能有所改善,但我想检查一下我是否正在使用正确提供的工具。

我特别担心我正在错误地处理队列。

我采取的方法。

  1. 初始化ConcurrentQueue
  2. 将ProcessImage方法添加到队列中 AddOnBeginRequestAsync中的BeginEventHandler
  3. 在中的EndEventHandler上处理队列 AddOnBeginRequestAsync
  4. 有很多代码,所以我提前道歉但异步编程很难:

    字段

    /// <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 init

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

    委托和处理

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

1 个答案:

答案 0 :(得分:1)

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

总而言之,您不需要ConcurrentQueue因为:

  1. 通过HttpModule的请求已经是并发的(来自Web主机架构)。
  2. 每个请求都在处理单个context实例。
  3. ProcessImage返回之前,您需要context的工作完成OnEndAsync
  4. 相反,您只想在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,因为它现在直接使用该方法。

    注意: contextProcessImage可能尚未为OnBeginAsync做好准备。如果是这种情况,则必须将ProcessImage移动为OnEndAsync内的简单同步调用。但是,也就是说,有可能通过一些并发来改进ProcessImage

    我要提出的另一个挑剔点是,hasAppStarted可以重命名为hasModuleInitialized,以减少歧义。