多线程.NET队列问题
本文关键字:问题 队列 NET 多线程 | 更新日期: 2023-09-27 17:59:47
我的代码中有一个wierd错误。这种情况极为罕见(可能每几周发生一次),但它确实存在,我不知道为什么。
我们有2个线程在运行,1个线程获取网络消息并将它们添加到队列中,如下所示:
DataMessages.Enqueue(new DataMessage(client, msg));
另一个线程从这个队列中取出消息并进行处理,如下所示:
while (NetworkingClient.DataMessages.Count > 0)
{
DataMessage message = NetworkingClient.DataMessages.Dequeue();
switch (message.messageType)
{
...
}
}
然而,每隔一段时间,我就会在switch (message.messageType)
行收到一个NullReferenceException,并且我可以在调试器中看到该消息为null。
不可能在队列中放入null值(请参阅代码的第一位),这是使用队列的仅有的两件事。
队列不是线程安全的吗?可能是我在另一个线程排队的确切时刻退出了队列,这导致了故障吗?
队列不是线程安全的,可能吗我正在排队另一个线程正在排队,并且这导致了故障?
没错。Queue
不是线程安全的。线程安全队列是System.Collections.Concurrent.ConcurrentQueue
。用它来解决你的问题。
while (NetworkingClient.DataMessages.Count > 0)
{
// once every two weeks a context switch happens to be here.
DataMessage message = NetworkingClient.DataMessages.Dequeue();
switch (message.messageType)
{
...
}
}
当您在该位置获得上下文切换时,第一个表达式的结果(NetworkingClient.DataMessages.Count > 0
)对于两个线程都为true,并且对Dequeue()
操作执行get的线程首先获取对象,第二个线程获取null(而不是InvalidOperationException,因为队列的内部状态没有完全更新以引发正确的异常)。
现在您有两个选项:
使用.NET 4.0并发队列
重构代码:
并使其看起来像这样:
while(true)
{
DataMessage message = null;
lock(NetworkingClient.DataMessages.SyncRoot) {
if(NetworkingClient.DataMessages.Count > 0) {
message = NetworkingClient.DataMessages.Dequeue();
} else {
break;
}
}
// .. rest of your code
}
编辑:更新以反映赫德尔的评论。
如果您对确切的原因感兴趣:
Enqueue
看起来像这样:
this._array[this._tail] = item;
this._tail = (this._tail + 1) % this._array.Length;
this._size++;
this._version++;
Dequeue
是这样的:
T result = this._array[this._head];
this._array[this._head] = default(T);
this._head = (this._head + 1) % this._array.Length;
this._size--;
this._version++;
比赛是这样的:
- 队列中有1个元素(head==tail),因此您的读取器线程开始出列,但在
Dequeue
中的第一行之后被中断 - 然后将另一个元素入队并放置在位置CCD_ 9,该位置此时等于CCD_
- 现在
Dequeue
恢复并用default(T)
覆盖Enqueue
刚刚插入的元素 - 下次调用dequeue时,会得到默认值(T)(在您的情况下为null),而不是实际值