5

私のアプリケーションには数年前から次のコードが含まれていますが、問題が発生したことはありません。

while ((PendingOrders.Count > 0) || (WaitHandle.WaitAny(CommandEventArr) != 1))
{
    lock (PendingOrders)
    {
       if (PendingOrders.Count > 0)
       {
           fbo = PendingOrders.Dequeue();
       }
       else
       {
           fbo = null;
       }
    }

    // Do Some Work if fbo is != null
}

CommandEventArrは、NewOrderEvent(自動リセットイベント)とExitEvent(手動リセットイベント)で構成されています。

しかし、これがスレッドセーフかどうかはわかりません(エンキューする前にすべてキューをロックするN個のプロデューサースレッドと、上記のコードを実行する1つのコンシューマースレッドを想定しています)。また、Queue.Countプロパティは、Queueクラスから1つのインスタンスInt32値を返すだけであると想定できます(揮発性、インターロック、ロックなどはありません)。

これを修正し、上記のコードで実行しようとしていることを実行するために、キューとAutoResetEventで使用される通常のパターンは何ですか?

(Queue.Countは何でも実行でき、その実装に固有であることが正しく指摘された後、質問を少し変更するように編集されました)。

4

4 に答える 4

4

私には非常にスレッドセーフに見えますが、WaitAny()は、イベントがすでに設定されているため、すぐに完了します。それは問題ではありません。

動作するスレッド同期を中断しないでください。しかし、より良いネズミ捕りが必要な場合は、この雑誌の記事でJoeDuffyのBlockingQueueを検討できます。より一般的なバージョンは、.NET 4.0、System.Collections.Concurrent.BlockingCollectionとConcurrentQueueで、その実用的な実装として利用できます。

于 2010-04-30T00:43:47.200 に答える
3

あなたは正しいです。コードはスレッドセーフではありません。しかし、あなたが考える理由ではありません。

AutoResetEventは問題ありません。ただし、ロックを取得してPendingOrders.Countを再テストしたからです。問題の本当の核心は、ロックの外でPendingOrders.Countを呼び出していることです。Queueクラスはスレッドセーフではないため、コードはスレッドセーフではありません...期間。

実際には、2つの理由から、これで問題が発生することはおそらくないでしょう。まず、Queue.Countプロパティは、ほぼ確実に、オブジェクトを半焼き状態のままにしないように設計されています。結局のところ、おそらくインスタンス変数を返すだけです。第2に、その読み取りにメモリバリアがないことは、コードのより広いコンテキストに大きな影響を与えることはありません。発生する最悪の事態は、ループの1回の反復で古い読み取りが発生し、取得したロックによって暗黙的にメモリバリアが作成され、次の反復で新しい読み取りが行われることです。ここでは、スレッドキューイングアイテムは1つだけであると想定しています。2つ以上あると状況が大きく変わります。

ただし、これを完全に明確にしておきます。実行中にPendingOrders.Countがオブジェクトの状態を変更しないという保証はありません。また、ロックにラップされていないため、ハーフバック状態のままで別のスレッドが操作を開始する可能性があります。

于 2010-04-30T02:30:01.740 に答える
2

手動イベントの使用...

ManualResetEvent[] CommandEventArr = new ManualResetEvent[] { NewOrderEvent, ExitEvent };

while ((WaitHandle.WaitAny(CommandEventArr) != 1))
{
    lock (PendingOrders)
    {
        if (PendingOrders.Count > 0)
        {
            fbo = PendingOrders.Dequeue();
        }
        else
        {
            fbo = null;
            NewOrderEvent.Reset();
        }
    }
}

次に、エンキュー側でもロックを確保する必要があります。

    lock (PendingOrders)
    {
        PendingOrders.Enqueue(obj);
        NewOrderEvent.Set();
    }
于 2010-04-29T22:24:35.650 に答える
0

そのためにのみWaitAnyを使用し、PendingOrdersコレクションに追加されたすべての新しい注文でシグナルが送信されるようにする必要があります。

while (WaitHandle.WaitAny(CommandEventArr) != 1))
{
   lock (PendingOrders)
   {
      if (PendingOrders.Count > 0)
      {
          fbo = PendingOrders.Dequeue();
      }
      else
      {
          fbo = null;

          //Only if you want to exit when there are no more PendingOrders
          return;
      }
   }

   // Do Some Work if fbo is != null
}
于 2010-04-29T22:22:32.003 に答える