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

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/13416889/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-08-10 08:30:02  来源:igfitidea点击:

Thread safe queue - Enqueue / Dequeue

c#multithreadingqueue

提问by Shane.C

Firstly, i'll explain a short scenario;

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

As a signal from certain devices triggers, an object of type Alarm is added to a queue. At an interval, the queue is checked, and for each Alarm in the queue, it fires a method.

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

However, the problem i'm running into is that, if an alarm is added to the queue whilst it's being traversed, it throws an error to say that the queue has changed whilst you were using it. Here's a bit of code to show my queue, just assume that alarms are being constantly inserted into it;

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

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

So my question is, how do i make this more...thread safe? So that i won't run into these issues. Perhaps something along the lines of, copying the queue to another queue, working on that one, then dequeueing the alarms that were dealt with from the original queue?

所以我的问题是,我如何使它更......线程安全?这样我就不会遇到这些问题。也许是将队列复制到另一个队列,处理那个队列,然后将处理过的警报从原始队列中取出?

edit: just been informed of concurrent queue, will check this out now

编辑:刚刚被告知并发队列,现在检查一下

采纳答案by Douglas

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

Alternatively, you could use:

或者,您可以使用:

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

Per the MSDN article on ConcurrentQueue<T>.GetEnumerator:

根据 MSDN 上的文章ConcurrentQueue<T>.GetEnumerator

The enumeration represents a moment-in-time snapshot of the contents of the queue. It does not reflect any updates to the collection after GetEnumeratorwas called. The enumerator is safe to use concurrently with reads from and writes to the queue.

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

Thus, the difference between the two approaches arises when your DeQueueAlarmmethod is called concurrently by multiple threads. Using the TryQueueapproach, you are guaranteed that each Alarmin the queue would only get processed once; however, which thread picks which alarm is determined non-deterministically. The foreachapproach ensures that each racing thread will process all alarms in the queue (as of the point in time when it started iterating over them), resulting in the same alarm being processed multiple times.

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

If you want to process each alarm exactly once, and subsequently remove it from the queue, you should use the first approach.

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

回答by jeroenh

.Net already has a thread-safe queue implementation: have a look at ConcurrentQueue.

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

回答by hometoast

Any reason you can't use ConcurrentQueue<T>

您不能使用的任何原因 ConcurrentQueue<T>

回答by Thought

A better way to approach it, given that each thread is actually only processing a single Alarm at once, would be replacing this:

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

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

with this:

有了这个:

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

There's no reason to get a complete snapshot of the queue at any time at all, because you only truly care about the next one to process at the beginning of each cycle.

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