0

私はマルチスレッドフォームアプリケーションを持っています.これは問題の部分がどのように設計されているかです:

スレッド 2 (BatchPreviewAssistant クラス) は、プライマリ インターフェイス スレッドが画像の読み込みタスクを渡すのを待機しています。タスクが受信されると、BatchPreviewAssistant は N=5 の待機中の PrimaryLoader スレッドにタスクを割り当てて有効にします。PrimaryLoaders は、_startEvent と _endEvent の 2 つの手動リセット イベントを使用して開始/停止された無限ループとして実行されています。また、N 個の手動リセット イベント _parentSyncEvent の配列があり、PrimaryLoaders から BatchPreviewAssistant への処理の終了を通知します。

したがって、通常、各 PrimaryLoader は _startEvent.WaitOne() で待機しています。BatchPreviewAssistant がそれらをアクティブにする必要があり、RunPrimaryLoaders() を実行すると、最初に _endEvent と _parentSyncEvents がリセットされ、次に _startEvent が設定されます。現在、WaitHandle.WaitAll(_parentSyncEvents) でブロックされます。_startEvent.Set() により、すべての PrimaryLoader が続行されます。各 PrimaryLoader が完了すると、5 つすべてが設定されるまで、_parentSyncEvent に独自のイベントが設定されます。この時点で、すべての PrimaryLoader が _endEvent.WaitOne(これで、_parentSyncEvents がすべて設定され、BatchPreviewAssistant が続行できるようになります。BatchPreviewAssistant は _startEvent をリセットし、_endEvent を設定して PrimaryLoaders を解放し、ループの先頭に戻ります。

BatchPreviewAssistant:

    private void RunPrimaryLoaders()
    {
        BatchPreviewThreadsLogger.WriteLog(Common.LogLevel.Debug1, "RunPrimaryLoaders()");
        ResetEvents(_parentSyncEvents);
        _endEvent.Reset();
        _startEvent.Set();

        // Primary Loader loops restart

        BatchPreviewThreadsLogger.WriteLog(Common.LogLevel.Debug2, "WaitHandle.WaitAll(_parentSyncEvent");
        if (!WaitHandle.WaitAll(_parentSyncEvents, 20 * 1000))
        {
            throw new TimeoutException("WaitAll(_parentSyncEvent) in ProcessCurrentCommand");
            // TODO: Terminate?
        }
        BatchPreviewThreadsLogger.WriteLog(Common.LogLevel.Message3, "Primary loading is complete");
        _startEvent.Reset();
        _endEvent.Set();
        bool isEndEventSet = _endEvent.WaitOne(0);
        BatchPreviewThreadsLogger.WriteLog(Common.LogLevel.Debug2, "isEndEventSet?" + isEndEventSet.ToString());
    }

プライマリローダー:

    public void StartProc(object arg)
    {
        while (true)
        {
            BatchPreviewThreadsLogger.WriteLog(Common.LogLevel.Debug2, "Primary Loader: _startEvent.WaitOne()");
            _startEvent.WaitOne();

            try
            {
                BatchPreviewThreadsLogger.WriteLog(Common.LogLevel.Message4, "Primary Loader is processing entry:" + processingEntry.DisplayPosition.ToString());
            }
            catch (Exception ex)
            {
                BatchPreviewThreadsLogger.WriteLog(Common.LogLevel.Error, "Exception in PrimaryImageLoader.StartProc:" + ex.ToString());
            }
            _parentSyncEvent.Set();
            BatchPreviewThreadsLogger.WriteLog(Common.LogLevel.Debug2, "Primary Loader: _endEvent.WaitOne()");
            _endEvent.WaitOne();
        }
    }

このコードは、このようなループを何百回も作成するのに非常にうまく機能しますが、特にストレステスト中に、時々問題が発生します。BatchPreviewAssistant が _endEvent.Set() を設定すると、_endEvent.WaitOne(); でどの PrimaryLoader も解放されません。BatchPreviewAssistant をチェックインすると、イベントが実際に設定されていることがわかりますが、PrimaryLoaders は解放されていません。

[10/27/2011;21:24:42.796;INFO ] [42-781:16]Primary Loader: _endEvent.WaitOne()
[10/27/2011;21:24:42.796;INFO ] [42-781:18]Primary Loader: _endEvent.WaitOne()
[10/27/2011;21:24:42.796;INFO ] [42-781:19]Primary Loader: _endEvent.WaitOne()
[10/27/2011;21:24:42.843;INFO ] [42-843:15]Primary Loader: _endEvent.WaitOne()
[10/27/2011;21:24:42.937;INFO ] [42-937:17]Primary Loader: _endEvent.WaitOne()
[10/27/2011;21:24:42.937;INFO ] [42-937:14]Primary loading is complete
[10/27/2011;21:24:42.937;INFO ] [42-937:14]isEndEventSet?True

このような設計に問題を引き起こす可能性のある明らかな問題はありますか? 回避策としていくつかの方法を試すことができますが、このアプローチの何が問題なのかを確認できれば幸いです。

念のため、PrimaryLoaders を初期化して開始する方法についても情報を提供しています。

private PrimaryImageLoader[] _primaryImageLoaders;

_primaryImageLoaders = new PrimaryImageLoader[N]

for (int i = 0; i < _primaryImageLoaderThreads.Length; i++)
{
  _parentSyncEvents[i] = new AutoResetEvent(false);
  _primaryImageLoaders[i] = new PrimaryImageLoader(i, _parentSyncEvents[i], 
      _startEvent, _endEvent,
      _pictureBoxes, _asyncOperation,
      LargeImagesBufferCount);
  _primaryImageLoaderThreads[i] = new Thread(new ParameterizedThreadStart(_primaryImageLoaders[i].StartProc));
  _primaryImageLoaderThreads[i].Start();
}

サンプルを簡略化するために、無関係なコードが削除されていることに注意してください。

追加: サンプルが忙しすぎて、理解するのが難しいことに同意します。つまり、これは一言で言えば次のとおりです。

Thread 2:
private void RunPrimaryLoaders()
{
  _endEvent.Reset();
  _startEvent.Set();

  _startEvent.Reset();
  _endEvent.Set();
  bool isEndEventSet = _endEvent.WaitOne(0);
}

Threads 3-7:
public void StartProc(object arg)
{
  while (true)
  {
    _startEvent.WaitOne();

    _endEvent.WaitOne();     // This is where it can't release occasionally although Thread 2 checks and logs that the event is set
  }
}
4

2 に答える 2

2

このような設計に問題を引き起こす可能性のある明らかな問題はありますか?

シンプルなことをしようとしているときに、非常に複雑なデザインを思いついているように見えます。単純な Producer/Consumer パターンの方がはるかにうまく機能し、この手動リセット イベントの災難に対処する必要はないようです。

おそらく、これに沿ってもっと何かが必要です:

class Producer
{
    private readonly BlockingQueue<Task> _queue;

    public Producer(BlockingQueue<Task> queue)
    {
        _queue = queue;
    }

    public LoadImages(List<Task> imageLoadTasks)
    {
        foreach(Task t in imageLoadTasks)
        {
            _queue.Enqueue(task);
        }
    }
}

class Consumer
{
    private volatile bool _running;
    private readonly BlockingQueue<Task> _queue;

    public Consumer(BlockingQueue<Task> queue)
    {
        _queue = queue;
        _running = false;
    }

    public Consume()
    {
        _running = true;

        while(_running)
        {
            try
            {
                // Blocks on dequeue until there is a task in queue
                Task t = _queue.Dequeue();

                // Execute the task after it has been dequeued
                t.Execute();
            }
            catch(ThreadInterruptedException)
            {
                // The exception will take you out of a blocking
                // state so you can check the running flag and decide
                // if you need to exit the loop or if you shouldn't.
            }
        }
    }
}

したがって、各Producerインスタンスを個別のスレッドで実行し、各Consumerインスタンスも独自のスレッドで実行する必要があります。もちろん、それらを適切に終了するには、すべての機能を追加する必要がありますが、それは別の話です。

于 2011-10-29T02:51:44.173 に答える
0

競合状態があります。条件を検出し、イベントをブロックするように設定し、イベントを待機するというロジックの場合、介入するロック解除が必要です。

あなたのコードはこれを行います:

  1. 待つことにする

  2. ブロックするイベントを設定

  3. イベント待ち

この問題は、ステップ 1 と 2 の間でイベントが発生した場合に発生します。イベントをブロックするように設定したときに、イベントが既に発生し、イベントのブロックが解除されている可能性があります。ステップ 3 に到達すると、既にブロック解除されたオブジェクトをブロック解除するために、既に発生したイベントを待機しています。悪い。

修正は次のとおりです。

  1. ロックを取得する

  2. 待つ必要がありますか?いいえの場合は、ロックを解除して戻ります

  3. ブロックするイベントを設定

  4. ロック解除

  5. イベント待ち

現在ロックを保持しているため、待機を決定してからイベントをブロックするように設定するまでの間、イベントは発生しません。イベントのブロックを解除するコードは、もちろん、イベントを処理してイベントのブロックを解除するロジックを通過するときに、同じロックを保持する必要があります。

于 2011-10-29T03:06:16.607 に答える