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

时间:2011-11-27 10:19:40

标签: c# .net windows multithreading thread-safety

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

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

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

如果你看一下这张照片,你会在白天看到它:

Weird thread behavior

你可以看到我用绿色框标记的位置,我尝试以三种不同的方式阅读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);
}

上面的代码片段都发生在工作线程中,正如您所看到的,我将buffer.Tag复制到message.Tag,我甚至设置了一些运行时检查以进行调试,检查{{1如果是这样,则为null值并将其id添加到名为“isNullLog”的列表中。在主线程中抛出NullReferenceException时,此列表为空。

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

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

peer.ReceivedMessages

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

老实说,我以前写过几个多线程应用程序并且从未遇到过这个问题。

稍微更新一下,我也尝试将public bool TryGetMessage(out TIncomingMessage message) { lock (ReceivedMessages) { if (ReceivedMessages.Count > 0) { message = ReceivedMessages.Dequeue(); return true; } } ReceivedMessageEvent.Reset(); message = null; return false; } 字段标记为Tag,使其看起来像volatile,但这似乎没有帮助。

1 个答案:

答案 0 :(得分:2)

我现在确实解决了这个问题,因为在处理线程时,你需要在读取/写入值时非常小心。我忘记清除接收循环中的本地message变量,最后在下一次循环迭代中“重用”相同的消息,因为它在每次迭代之前进行if(message == null) { /* create new message */ }检查,当我不是清除此读取线程最终践踏了在尝试向其写入新消息时存储在此处的“旧”消息!