2013-03-20 5 views
3

Я пытаюсь добавить ускорение скорости к HttpModule, который обрабатывает изображения с использованием асинхронного программирования.Правильное использование ConcurrentQueue в HttpModule?

Несмотря на то, что я уверен, что получаю повышение производительности, я хотел бы проверить, что я правильно использую инструменты.

Я особенно обеспокоен тем, что обрабатываю очередь неправильно.

Подход, который я принимаю.

  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; 

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

делегатом и р 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, который будет один экземпляр на вызов к вашему HttpModule. OnBeginAsync вашего HttpModule вызывается каждый веб-запрос по мере необходимости, и ваш делегат уже дает вам логику для выполнения асинхронной операции. Это означает, что вам не нужны 4 параллельных потока, потому что у вас есть только один экземпляр context, чтобы работать, во всяком случае. И нам не нужен ConcurrentQueue, потому что все работы над context должны быть завершены в течение срока действия запроса-ответа.

Подводя итог, вам не нужно ConcurrentQueue потому что:

  1. просит через HttpModule уже одновременно (от архитектуры веб-хоста).
  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> imageOperations и Enqueue, и вы можете также переименовать EnqueueDelegate быть ProcessImageDelegate, так как он работает непосредственно с этим методом, в настоящее время.

Примечание: Это может быть, что ваш context не готов к ProcessImage во время OnBeginAsync. Если это так, вам нужно будет переместить ProcessImage в качестве простого синхронного вызова в пределах OnEndAsync. Однако, тем не менее, существует реальная возможность того, что ProcessImage можно улучшить с помощью некоторого параллелизма.

Еще один пункт, который я сделал бы, это то, что hasAppStarted можно было бы переименовать hasModuleInitialized, чтобы быть менее двусмысленным.

+0

Спасибо за это, да, я обязательно изменю имя поля. :) Вы очень сильно подхватили то, что меня беспокоило в «OnEndAsync». Я получил 4 потока бит из образца в MSDN, и я не был уверен, что это было либо выгодно, либо вредно для моего процесса. Сказав это, сокращение процесса до одного потока, похоже, ускорило мою тестовую страницу на ~ 20%. Будет ли иметь смысл сохранить только один или вы могли бы предложить способ, которым больше потоков можно было бы использовать более выгодным образом? –

+0

Спасибо за это ... К сожалению, ваше редактирование что-то сломало. Изображения, безусловно, обрабатываются и сохраняются в файловой системе, но теперь они возвращаются в браузер в необработанной необработанной форме. –

+0

Да, это имеет смысл. Вернемся к чертежной доске, если мне нужна дополнительная скорость:/ –

 Смежные вопросы

  • Нет связанных вопросов^_^