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)。

现在您有两种选择:

  1. 使用.NET 4.0 ConcurrentQueue

  2. 重构你的代码:

并让它看起来像这样:

 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)而不是实际值