multithreading.NET队列问题
我的代码中有一个奇怪的错误。 这是非常罕见的(可能每隔几周发生一次),但它在那里,我不知道为什么。
我们有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。
将空值放入队列是不可能的(参见代码的第一位),这些是使用队列的唯一两件事。
队列是不是线程安全的,是不是我在另一个线程入队的确切时刻出列,这会导致故障?
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的内部状态没有完全更新以抛出正确的exception)。
现在您有两种选择:
-
使用.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
不是线程安全的。 线程安全队列是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),因此您的读者线程开始出列但在
Dequeue
的第一行后被中断 - 然后将另一个元素排队并放置在此时等于
head
位置tail
。 - 现在
Dequeue
恢复并覆盖由Enqueue
插入的元素,default(T)
- 下次调用dequeue时,您将获得默认值(T)(在您的情况下为null)而不是实际值