4

非同期プログラミングを使用して画像を処理する HttpModule に速度向上を追加しようとしています。

確かにパフォーマンスが向上しているように見えますが、提供されたツールを正しく使用していることを確認したいと思います。

キューを正しく処理していないことが特に心配です。

私が取っているアプローチ。

  1. ConcurrentQueue を初期化する
  2. AddOnBeginRequestAsync の BeginEventHandler のキューに ProcessImage メソッドを追加します。
  3. AddOnBeginRequestAsync の EndEventHandler でキューを処理する

コードがたくさんあるので、前もってお詫びしますが、非同期プログラミングは難しいです:

田畑

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

委任して処理する

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

1 に答える 1

2

ProcessImageメソッドは で動作するように見えます。HttpContextこれは、HttpModule への呼び出しごとに 1 つのインスタンスになります。HttpModuleのOnBeginAsyncは必要に応じてすべての Web リクエストで呼び出され、デリゲートは既に非同期操作を実行するためのロジックを提供しています。contextつまり、処理するインスタンスは 1 つしかないため、4 つの同時スレッドは必要ありません。ConcurrentQueueまた、 に対するすべての作業contextは、リクエスト/レスポンスの有効期間内に完了する必要があるため、 は必要ありません。

ConcurrentQueue要約すると、次の理由で必要ありません。

  1. HttpModule を介したリクエストは、(Web ホスト アーキテクチャから) 既に同時実行されています。
  2. 各リクエストは 1 つのcontextインスタンスで処理されます。
  3. から戻る前に、 での作業をProcessImage完了する必要があります。contextOnEndAsync

代わりに、メソッドでバックグラウンド作業を開始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> imageOperationsandを削除して、そのメソッドで直接動作するため、名前を be にEnqueue変更することもできます。EnqueueDelegateProcessImageDelegate

注:の時点でcontext準備が整っていない可能性があります。その場合は、内で単純な同期呼び出しとして移動する必要があります。しかし、そうは言っても、いくつかの並行性によって改善できる可能性は現実にあります。ProcessImageOnBeginAsyncProcessImageOnEndAsyncProcessImage

私が作るもう1つの気の利いたポイントは、あいまいさを少なくするために名前をhasAppStarted変更できるということです。hasModuleInitialized

于 2013-03-20T17:48:42.317 に答える