问题描述
|
我的代码中出现一个奇怪的错误。它非常罕见(也许每隔几周发生一次),但是它在那里而且我不确定为什么。
我们有2个线程在运行,有1个线程获取网络消息并将其添加到如下队列中:
DataMessages.Enqueue(new DataMessage(client,msg));
另一个线程将消息从此队列中移出并进行处理,如下所示:
while (NetworkingClient.DataMessages.Count > 0)
{
DataMessage message = NetworkingClient.DataMessages.Dequeue();
switch (message.messageType)
{
...
}
}
但是,每隔一段时间我就会在第2行上得到NullReferenceException,并且在调试器中可以看到该消息为null。
不可能将空值放入队列(请参见代码的第一位),而这是使用队列的仅有的两件事。
Queue是否不是线程安全的,是否可能是我在另一个线程入队的确切时刻出队,这会导致故障?
解决方法
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
)对于两个线程都是正确的,而执行Dequeue()
操作的线程首先获取对象,第二个线程获取null(而不是InvalidOperationException,因为Queue的内部状态为\尚未完全更新以引发正确的异常)。
现在,您有两个选择:
使用.NET 4.0 ConcurrentQueue
重构代码:
并使其看起来像这样:
while(true)
{
DataMessage message = null;
lock(NetworkingClient.DataMessages.SyncRoot) {
if(NetworkingClient.DataMessages.Count > 0) {
message = NetworkingClient.DataMessages.Dequeue();
} else {
break;
}
}
// .. rest of your code
}
编辑:更新以反映Heandel的评论。
,
Queue不是线程安全的吗?
我正要出队
另一个线程正在排队,
这会导致故障吗?
究竟。 Queue
不是线程安全的。线程安全队列为System.Collections.Concurrent.ConcurrentQueue
。改用它可以解决您的问题。
, 如果您对确切原因感兴趣:
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),因此您的阅读器线程开始出队,但在ѭ11中的第一行之后被中断
然后,将另一个元素放入队列,并将其放置在位置tail
处,该位置等于head
。
现在,Dequeue
恢复并用ѭ18inserted覆盖Enqueue
刚刚插入的元素。
下次调用出队时,将获得默认值(T)(在您的情况下为null),而不是实际值