2013-03-20 50 views
3

我正在嘗試向使用異步編程處理圖像的HttpModule添加速度提升。正確使用HttpModule中的ConcurrentQueue?

儘管看起來我正在獲得性能提升,但我想檢查一下我是否使用了正確提供的工具。

我特別擔心我正在處理隊列不正確。

我正在採取的方法。

  1. 初始化的ConcurrentQueue
  2. 添加processImage來法隊列上 的BeginEventHandler在AddOnBeginRequestAsync
  3. 進程隊列上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; 

HTTP模塊初始化

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

委託和對rocess

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

它看起來像你的ProcessImage方法適用於HttpContext,這將是每個呼叫的單個實例到你的HTTP模塊。您的HttpModule的OnBeginAsync根據需要被調用每個Web請求,並且您的委託已經爲您提供執行異步操作的邏輯。這意味着,您不需要4個併發線程,因爲只有一個context實例可以工作。我們不需要ConcurrentQueue,因爲context上的所有工作應該在請求 - 響應的生命週期中完成。

綜上所述,你不需要ConcurrentQueue因爲:

  1. 請求通過HTTP模塊已經併發(從web主機體系結構)。
  2. 每個請求正在處理單個context實例。
  3. 您需要從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,你也可以重新命名EnqueueDelegateProcessImageDelegate,因爲它與該方法直接工作,現在。

注:這可能是因爲你的context是沒有準備好ProcessImageOnBeginAsync的時間。如果是這種情況,則必須在OnEndAsync之內將ProcessImage作爲簡單的同步呼叫。然而,這就是說,有可能通過一些併發來改進ProcessImage

我會做的另一個挑剔點是hasAppStarted可能會被重命名爲hasModuleInitialized不太模糊。

+0

謝謝你,是的,我一定會改變字段名稱。 :)你在'OnEndAsync'上找到的東西很多,我從MSDN的示例中得到了4個線程位,我不確定它對我的過程是有利的還是有害的。話雖如此,將過程簡化爲一個線程似乎已使我的測試頁面加速了大約20%。保持一個還是可以的,或者你能否提出一種更多線程可以以更有利的方式使用的方法? – 2013-03-20 19:11:02

+0

謝謝你......不幸的是,你的編輯似乎已經打破了一些東西。這些圖像肯定會得到處理並保存到文件系統,但現在將以原始未處理的形式返回給瀏覽器。 – 2013-03-20 20:17:10

+0

是的,這是有道理的。回到繪圖板,如果我想要更多的速度:/ – 2013-03-20 20:46:53