web-dev-qa-db-ja.com

スレッドセーフキュー-エンキュー/デキュー

最初に、短いシナリオを説明します。

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

しかし、私が遭遇している問題は、通過中にキューにアラームが追加されると、使用中にキューが変更されたことを示すエラーがスローされることです。ここに私のキューを表示するためのコードが少しありますが、アラームが常にキューに挿入されていると仮定してください。

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
        {
        }
    }

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

編集:ちょうど並行キューが通知された、今これをチェックアウトします

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

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

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

ConcurrentQueue<T>.GetEnumerator

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

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

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

26
Douglas

使用できない理由 ConcurrentQueue<T>

24
hometoast

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

7
jeroenh

各スレッドが実際に一度に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);
        }

キューの完全なスナップショットをいつでも取得する理由はありません。各サイクルの最初に処理する次のものだけを本当に気にするからです。

0
Thought