2011-11-27 41 views
6

我有一個令人難以置信的奇怪的NullReferenceException在從我知道存在的對象上的公共字段讀取值時拋出。基本流程是這樣的:奇怪的線程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.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; 
} 

你可以看到,我鎖定隊列甚至在我檢查計數,如果不是空的,則設置out屬性並返回true,否則返回false。

老實說我完全難住了,之前寫過幾個多線程的應用程序,從來沒有遇到過這個。

稍微更新一下,我也試過把Tag這個字段標記爲volatile,看起來像這樣public volatile object Tag;,但這似乎沒有幫助。

+2

我不是一個學者,但我認爲你正在混合鎖與易失性讀寫..我認爲這是問題所在。如果你使用volatile,你最好一直使用它..如果你正在使用鎖,那麼只鎖.. – 2011-11-27 10:47:15

+0

嗯,不知道我是否同意 - 我鎖定所有線程都會見,volatile部分似乎不起作用它根本(如果我刪除它或添加它,沒有任何反應)。 – thr

+1

是否有另一個線程對'message.Tag'字段進行操作(例如將其設置爲空)? – Hans

回答

2

我現在確實已經解決了這個問題,就像往常一樣,在處理線程時,您需要在讀取/寫入值時非常小心。我忘記清除接收循環中的本地message變量,並在下一次循環迭代中結束「重複使用」相同的消息,因爲它在每次迭代之前都有一個if(message == null) { /* create new message */ }檢查,並且當我沒有清除該讀取線程時,結束了踐踏當試圖向它寫一條新消息時,存儲在這裏的所有「舊」消息!

+1

你可以有太多的檢查:)不要費心檢查或清除消息*對象實例變量 - 只是習慣於在開始時創建一個新實例,以及在排隊之後的下一個步驟。 –