30

まず、短いシナリオを説明します。

特定のデバイスからの信号がトリガーされると、アラーム タイプのオブジェクトがキューに追加されます。一定の間隔でキューがチェックされ、キュー内のアラームごとにメソッドが起動されます。

ただし、私が直面している問題は、トラバース中にキューにアラームが追加された場合、使用中にキューが変更されたことを示すエラーがスローされることです。キューを表示するためのコードを次に示します。アラームが常にキューに挿入されていると仮定してください。

public class AlarmQueueManager
{
    public ConcurrentQueue<Alarm> alarmQueue = new ConcurrentQueue<Alarm>();
    System.Timers.Timer timer;

    public AlarmQueueManager()
    {
        timer = new System.Timers.Timer(1000);
        timer.Elapsed += new System.Timers.ElapsedEventHandler(timer_Elapsed);
        timer.Enabled = true;
    }

    void timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
    {
        DeQueueAlarm();
    }

    private void DeQueueAlarm()
    {
        try
        {
            foreach (Alarm alarm in alarmQueue)
            {
                SendAlarm(alarm);
                alarmQueue.TryDequeue();
                //having some trouble here with TryDequeue..

            }
        }
        catch
        {
        }
    }

だから私の質問は、どうすればこれをもっと...スレッドセーフにすることができますか? 私がこれらの問題に遭遇しないように。おそらく、キューを別のキューにコピーし、そのキューで作業してから、元のキューから処理されたアラームをデキューするようなものでしょうか?

編集: 同時実行キューについて通知されたばかりです。今すぐ確認します

4

4 に答える 4

32
private void DeQueueAlarm()
{
    Alarm alarm;
    while (alarmQueue.TryDequeue(out alarm))
        SendAlarm(alarm);
}

または、次を使用できます。

private void DeQueueAlarm()
{
    foreach (Alarm alarm in alarmQueue)
        SendAlarm(alarm);
}

MSDNの記事によるとConcurrentQueue<T>.GetEnumerator

列挙は、キューの内容の瞬間的なスナップショットを表します。GetEnumeratorが呼び出された後のコレクションへの更新は反映されません。列挙子は、キューからの読み取りおよびキューへの書き込みと同時に安全に使用できます。

DeQueueAlarmしたがって、メソッドが複数のスレッドによって同時に呼び出されると、2 つのアプローチの違いが生じます。このアプローチを使用すると、キュー内のTryQueueそれぞれが 1 回だけ処理されることが保証されます。Alarmただし、どのスレッドがどのアラームを選択するかは、非決定論的に決定されます。このforeachアプローチにより、各レーシング スレッドがキュー内のすべてのアラームを処理することが保証され (反復処理を開始した時点で)、同じアラームが複数回処理されることになります。

各アラームを 1 回だけ処理し、その後キューから削除する場合は、最初の方法を使用する必要があります。

于 2012-11-16T13:10:21.453 に答える
30

使えない理由ConcurrentQueue<T>

于 2012-11-16T12:49:23.847 に答える
10

.Netにはすでにスレッドセーフなキューの実装があります。ConcurrentQueueを見てください

于 2012-11-16T12:49:18.883 に答える
0

各スレッドが実際には一度に 1 つのアラームしか処理していないことを考えると、これを置き換えるより良い方法は次のとおりです。

        foreach (Alarm alarm in alarmQueue)
        {
            SendAlarm(alarm);
            alarmQueue.TryDequeue();
            //having some trouble here with TryDequeue..
        }

これとともに:

        while (!alarmQueue.IsEmpty)
        {
            Alarm alarm;
            if (!alarmQueue.TryDequeue(out alarm))  continue;
            SendAlarm(alarm);
        }

キューの完全なスナップショットをいつでも取得する必要はまったくありません。各サイクルの開始時に処理する次のスナップショットのみを本当に気にするからです。

于 2015-06-29T17:58:01.687 に答える