线程安全队列 - 入队/出队

时间:2012-11-16 12:47:28

标签: c# multithreading queue

首先,我将解释一个简短的场景;

当来自某些设备的信号触发时,会将类型为Alarm的对象添加到队列中。每隔一段时间检查一次队列,对于队列中的每个警报,它会触发一个方法。

但是,我遇到的问题是,如果在遍历队列时向队列添加了警报,则会在您使用它时发出错误说队列已更改。这里有一些显示我的队列的代码,只是假设警报不断插入其中;

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 个答案:

答案 0 :(得分:25)

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上的MSDN文章:

  

枚举表示队列内容的即时快照。在调用GetEnumerator后,它不会反映对集合的任何更新。枚举器可以安全地与队列的读取和写入同时使用。

因此,当多个线程同时调用DeQueueAlarm方法时,会出现两种方法之间的差异。使用TryQueue方法,可以保证队列中的每个Alarm只会被处理一次;但是,哪个线程选择哪个警报是非确定性的。 foreach方法确保每个赛车线程将处理队列中的所有警报(从它开始迭代它们的时间点开始),导致多次处理相同的警报。

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

答案 1 :(得分:22)

您无法使用ConcurrentQueue<T>

的任何原因

答案 2 :(得分:7)

.Net已经有一个线程安全的队列实现:看看ConcurrentQueue

答案 3 :(得分: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);
        }

根本没有理由在任何时候获得队列的完整快照,因为您只关心在每个周期开始时要处理的下一个队列。