2017-03-20 70 views
1

我遇到了一個非常奇怪的(對我來說)異常。這種事很少,但確實...默認構造函數後的空引用異常

我的類不是靜態的,而是隻有一個靜態屬性:

static Dictionary<string, ManualResetEvent> resetEvents = 
    new Dictionary<string, ManualResetEvent>(); 

當我嘗試添加首次復位事件 - 我有時會得到一個空引用異常。可能這與兩個不同的線程試圖添加實例有關?

static ManualResetEvent resetEventsGet(string key) 
{ 
    if (resetEvents.ContainsKey(key)) 
     return resetEvents[key]; 
    ManualResetEvent reste = new ManualResetEvent(false); 
    resetEvents.Add(key, reste); //System.NullReferenceException: 'Object reference not set to an instance of an object.' HOW??? 
    return reste; 
} 

當我在「watch」或即時窗口中查找時,在任何地方都沒有空(字典或resetEvent)。

p.s - 我將它標記爲visual studio 2017,因爲它之前從未發生過,儘管代碼沒有改變。 有什麼想法?謝謝

+0

這很奇怪,因爲它好像超過了'ContainsKey'調用。你在哪裏沒有'resetEvents = null'? – juharr

+0

這對我沒有意義。爲什麼這個異常不在'if(resetEvents.ContainsKey(key))'行中?你確定你沒有從代碼中的其他任何地方使該字段爲空嗎? – dcg

+0

對'new ManualResetEvent(false)'的調用是做什麼的?它不會使字段'null'? – Marcello

回答

4

如果您從多個線程中調用resetEventsGet,那完全可能。 Dictionary.Add不是線程安全的,當你從多個線程調用它時 - 可能會發生奇怪的事情,包括拋出'NullReferenceException'。這是比較容易再現與下面的代碼:

class Program { 
    static Dictionary<string, ManualResetEvent> resetEvents = new Dictionary<string, ManualResetEvent>(); 

    static void Main() 
    { 
     for (int i = 0; i < 1000; i++) { 
      new Thread(() => 
      { 
       resetEvents.Add(Guid.NewGuid().ToString(), new ManualResetEvent(false)); 
      }) 
      { 
       IsBackground = true 
      }.Start(); 
     } 
     Console.ReadKey(); 
    }  
} 

此代碼並非總是如此,但很多時候,拋出裏面Dictionary.Insert私有方法空引用異常。

發生這種情況是因爲字典將數值存儲在類似數組的內部結構中,並且這些結構的大小不是固定的。當你添加更多的值時 - 字典可能調整它的內部結構,當另一個線程同時枚舉它們時,調整大小可能會發生。在同一時間調整大小和枚舉可能導致許多不好的事情,包括空引用或索引超出範圍的異常。

所以只是不要這樣做,並使用適當的鎖定。或者使用專爲多線程訪問而設計的集合,如ConcurrentDictionary<string, ManualResetEvent>

+0

謝謝!終於設法測試 - 其實我沒有任何例外的Visual Studio 2008(framWork 3.5)。在2017年,我幾乎沒有(在啓動一系列Guid之前的循環之後),除了一些沒有輸入的keyValues(試圖填充1000並且僅發現998 ...沒有任何例外)。 Intresting。再次感謝。很明顯,我現在添加lock \ ConcurrentDictionary。 – ephraim

+0

Ha!終於在VS 2008中發生了......我認爲實際的區別是我從來沒有在發佈模式下運行VS 2008,但是2017年我做到了......現在它在2008年發佈了發佈模式。 – ephraim

3

如果您使用多個線程訪問它,最好鎖定它。問題是,字典不是線程安全的。在這種情況下,您可以使用Dictionary本身作爲lockobject。 (因爲它是私有的)

喜歡的東西:

static ManualResetEvent resetEventsGet(string key) 
{ 
    lock(resetEvents) 
    { 
     ManualResetEvent result; 

     // lookup the key 
     if(!resetEvents.TryGetValue(key, out result)) 
      // if it doesn't exists, create a new one. 
      resetEvents.Add(key, result = new ManualResetEvent(false)); 

     return result; 
    } 
} 

而且TryGetValue是巨大的。它給你的價值,如果它存在。 (所以只有一個查找,而不是兩個)