多线程.NET队列问题

Han*_*esh 16 .net c# queue multithreading

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

我们有2个线程正在运行,1个线程获取网络消息并将它们添加到队列中,如下所示:

DataMessages.Enqueue(new DataMessage(client, msg));
Run Code Online (Sandbox Code Playgroud)

另一个线程将消息从此队列中取出并处理它们,如下所示:

while (NetworkingClient.DataMessages.Count > 0)
{
    DataMessage message = NetworkingClient.DataMessages.Dequeue();

    switch (message.messageType)
    {
       ...
    }
}
Run Code Online (Sandbox Code Playgroud)

但是,每隔一段时间我就会在行上得到一个NullReferenceException switch (message.messageType),我可以在调试器中看到该消息为null.

将空值放入队列是不可能的(参见代码的第一位),这些是使用队列的唯一两件事.

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

use*_*016 11

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

究竟.Queue不是线程安全的.一个线程安全的队列是System.Collections.Concurrent.ConcurrentQueue.用它代替来解决你的问题.

  • 不要盲目使用`ConcurrentQueue`; 只有在你知道自己在做什么的时候才使用它.在某些情况下,带锁的`Queue`是更好的选择.并发集合类不是并发问题的奇迹治疗:) (2认同)

Kar*_*gha 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)
        {
           ...
        }
    }
Run Code Online (Sandbox Code Playgroud)

...当你在该位置获得上下文切换时,第一个表达式(NetworkingClient.DataMessages.Count > 0)的结果对于两个线程都是真的,并且获取Dequeue()操作的第一个得到的是对象而第二个线程得到的是null(而不是InvalidOperationException,因为Queue的内部状态未完全更新以抛出正确的异常).

现在您有两个选择:

  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
}
Run Code Online (Sandbox Code Playgroud)

编辑:更新以反映Heandel的评论.


Chr*_*Wue 7

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

Enqueue 看起来像这样:

this._array[this._tail] = item;
this._tail = (this._tail + 1) % this._array.Length;
this._size++;
this._version++;
Run Code Online (Sandbox Code Playgroud)

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++;
Run Code Online (Sandbox Code Playgroud)

比赛是这样的:

  • 队列中有1个元素(head == tail),因此您的阅读器线程开始出列但在第一行之后被中断 Dequeue
  • 然后将另一个元素排队并放置在此时tail等于的head位置.
  • 现在Dequeue重新开始,并覆盖其刚刚被插入的元素Enqueuedefault(T)
  • 下次调用dequeue时,您将获得默认值(T)(在您的情况下为null)而不是实际值