首先,我将解释一个简短的场景;
当来自某些设备的信号触发时,会将类型为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
{
}
}
所以我的问题是,我如何让这更多......线程安全?所以我不会遇到这些问题。也许有些事情,将队列复制到另一个队列,处理那个队列,然后将从原始队列中处理的警报出列?
编辑:刚刚被告知并发队列,现在将检查出来
答案 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);
}
根本没有理由在任何时候获得队列的完整快照,因为您只关心在每个周期开始时要处理的下一个队列。