我试图为使用异步编程来处理图像的HttpModule添加速度提升。
虽然看起来确实可以改善性能,但我想检查自己是否使用了正确提供的工具。
我特别担心我处理队列不正确。
我正在采用的方法。
AddOnBeginRequestAsync
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的
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> imageOperations
和Enqueue
,也可以将EnqueueDelegate
重命名为ProcessImageDelegate
,因为现在它可以直接与该方法一起使用。注意:可能是在
context
时您的ProcessImage
尚未准备好使用OnBeginAsync
。如果是这种情况,您将必须将ProcessImage
作为简单的同步调用移动到OnEndAsync
中。但是,也就是说,确实有可能通过某种并发来改善ProcessImage
。我要指出的另一点是,可以将
hasAppStarted
重命名为hasModuleInitialized
,以减少歧义。