多线程.NET队列问题

时间:2011-06-18 17:08:06

标签: c# .net multithreading queue

我的代码中有一个奇怪的错误。这是非常罕见的(可能每隔几周发生一次),但它就在那里,我不知道为什么。

我们有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,我可以在调试器中看到该消息为空。

无法将空值放入队列(请参阅代码的第一位),这些是使用该队列的唯一两件事。

队列是不是线程安全的,是不是我在另一个线程入队的确切时刻出列,这会导致故障?

3 个答案:

答案 0 :(得分:11)

  

Queue不是线程安全的,不是吗?   我正准备在那一刻出发   另一个线程入队和   这会导致故障吗?

完全。 Queue不是线程安全的。线程安全队列为System.Collections.Concurrent.ConcurrentQueue。请改用它来解决问题。

答案 1 :(得分:9)

    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()操作的第一个获取对象而第二个线程获得空(而不是InvalidOperationException,因为队列的内部状态未完全更新以抛出正确的异常。)

现在您有两个选择:

  1. 使用.NET 4.0 ConcurrentQueue

  2. 重构您的代码:

  3. 让它看起来像这样:

    while(true)
    {
      DataMessage message = null;
    
      lock(NetworkingClient.DataMessages.SyncRoot) {
           if(NetworkingClient.DataMessages.Count > 0) {
              message = NetworkingClient.DataMessages.Dequeue();
           } else {
             break;
           }
        }
        // .. rest of your code
    }
    

    编辑:更新以反映亨德尔的评论。

答案 2 :(得分:7)

如果您对确切原因感兴趣:

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
  • 中的第一行后被中断
  • 然后将另一个元素排队并放在tail的位置,此时此位置等于head
  • 现在Dequeue恢复并覆盖Enqueuedefault(T)
  • 刚刚插入的元素
  • 下次调用dequeue时,您将获得默认值(T)(在您的情况下为null)而不是实际值