读取存在的值时奇怪的线程NullReferenceException?

thr*_*thr 7 .net c# windows multithreading thread-safety

我从一个我知道存在的对象的公共字段读取值时抛出了一个令人难以置信的奇怪的NullReferenceException.基本流程如下:

编辑:我意识到我忘记提到一些重要的事情,每次我尝试读取Tag值时都不会发生这种情况,但只有somtimes,足以让我每次都可以通过运行代码重现它,但代码运行时不会立即重现

  • 服务器收到一条消息 (工作线程)
  • 发送消息get的连接被设置为Tag消息对象上的字段 (工作线程)
  • 消息被放入"ReceivedMessages"队列(一个正常的Queue对象,由用于序列化访问的锁保护)(工作线程)
  • 消息被读取(主线程)
  • 我尝试读取Tag消息的字段以获取连接,这有时会返回null并抛出异常,但是当抛出异常并检查Message对象时,我可以看到Connection对象(该Tag字段中的对象)那天清楚(主线程)

如果你看一下这张照片,你会看到它清晰的日子:

奇怪的线程行为

您可以看到我用绿色框标记的位置,我尝试以message.Tag三种不同的方式读取属性,它们都返回null,因为您可以在标有蓝色框的部分中看到.

但是,如果您查看标记为红色的两个区域,您可以看到该对象实际存在的日期.并且,为了清除任何混淆,消息放在收到的消息队列中的部分如下所示:

我可以看到我甚至尝试使用Thread.VolatileWrite来确保写入值

message.Tag = buffer.Tag;

Thread.VolatileWrite(ref message.Tag, buffer.Tag);

if (message.Tag == null)
{
    isNullLog.Add(message.Id);
}

// Queue into received messages
lock (peer.ReceivedMessages)
{
    peer.ReceivedMessages.Enqueue(message);
}
Run Code Online (Sandbox Code Playgroud)

上面的代码片段都发生在工作线程中,正如你所看到的,我将其复制buffer.Tagmessage.Tag,我甚至设置了一个运行时检查调试,检查是否message.Tag为空值并将其id添加到名为"isNullLog"的列表中情况也是如此.在主线程中抛出NullReferenceException时,此列表为空.

您还看到我在设置字段锁定peer.ReceivedMessages队列并将消息推送到队列.message.Tag

此外,这里更清楚的是用于从peer.ReceivedMessages队列中读出消息的函数:

public bool TryGetMessage(out TIncomingMessage message)
{
    lock (ReceivedMessages)
    {
        if (ReceivedMessages.Count > 0)
        {
            message = ReceivedMessages.Dequeue();
            return true;
        }
    }

    ReceivedMessageEvent.Reset();

    message = null;
    return false;
}
Run Code Online (Sandbox Code Playgroud)

你可以看到我在检查计数之前锁定队列,如果它不是空的,我设置out属性并返回true,否则我返回false.

老实说,我完全难过,以前编写了几个多线程应用程序,从未遇到过这种情况.

有点更新,我也尝试将Tag字段标记为volatile,使它看起来像这样,public volatile object Tag;但这似乎没有帮助.

thr*_*thr 2

我现在确实修复了这个问题,一如既往,在处理线程时,您在读取/写入值时需要非常小心。我忘记清除message接收循环中的局部变量,并最终在下一个循环迭代中“重用”相同的消息,因为它在if(message == null) { /* create new message */ }每次迭代之前都有一个检查,当我没有清除它时,读取线程最终会被践踏尝试向其中写入新消息时存储在这里的“旧”消息!