Правильное использование ConcurrentQueue в HttpModule?



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



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



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



Подход, который я использую.




  1. Init The ConcurrentQueue

  2. добавьте метод ProcessImage в очередь on
    то 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);
}


Делегирование и процесс



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

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, чтобы быть менее двусмысленным.

Comments

    Ничего не найдено.