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因此,当您的方法被多个线程同时调用时,两种方法之间的差异就会出现。使用该TryQueue方法,您可以保证Alarm队列中的每个都只会被处理一次;然而,哪个线程选择哪个警报是不确定的。该foreach方法确保每个竞赛线程将处理队列中的所有警报(从它开始迭代它们的时间点开始),从而导致多次处理相同的警报。

如果您想只处理每个警报一次,然后将其从队列中删除,您应该使用第一种方法。

于 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

考虑到每个线程实际上一次只处理一个警报,一个更好的方法是替换它:

        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 回答