默认构造函数后的空引用异常

时间:2017-03-20 15:00:13

标签: c# thread-safety visual-studio-2017

我遇到了一个非常奇怪的(对我来说......)例外。它很少发生,但确实......

我的类不是静态的,但只有一个静态属性:

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;
}

当我在“观察”或即时窗口中查看时,任何地方都没有空(字典或resetEvent)。

p.s - 我为Visual Studio 2017标记了它,因为它之前从未发生过,尽管代码没有改变。 任何的想法?感谢

2 个答案:

答案 0 :(得分:4)

如果你从多个线程中调用resetEventsGet,这是完全可能的。 Dictionary.Add不是线程安全的,当你从多个线程调用它时 - 可能会发生奇怪的事情,包括抛出NullReferenceException&#39;。使用以下代码重现起来相对容易:

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>

答案 1 :(得分:3)

如果您使用多个线程访问它,则最好将其锁定。问题是,字典不是线程安全的。在这种情况下,您可以将Dictionary本身用作lockobject。 (因为它是私人的)

类似于:

TryGetValue

grammar Test; r : specification+; specification : MODULE module_def EOF; module_def : ID EQ classExp; classExp : basicClassExp | altClassExp ; basicClassExp : CLASS (classCode)* END; altClassExp : ALTCLASS (classCode)* END; classCode : 'classCode'; EXCLAMATION : '!'; EQ : '='; MODULE : 'module'; END : 'end'; CLASS : 'class'; ALTCLASS : 'altclass'; ID : JavaLetter JavaLetterOrDigit* ; fragment JavaLetter : [a-zA-Z$_] | ~[\u0000-\u007F\uD800-\uDBFF] | [\uD800-\uDBFF] [\uDC00-\uDFFF] ; fragment JavaLetterOrDigit : [a-zA-Z0-9$_] | ~[\u0000-\u007F\uD800-\uDBFF] | [\uD800-\uDBFF] [\uDC00-\uDFFF] ; WS : [ \t\r\n\u000C]+ -> skip ; COMMENT : '/*' .*? '*/' -> channel(HIDDEN) ; LINE_COMMENT : '//' ~[\r\n]* -> channel(HIDDEN) ; 也很棒。它给你价值,如果它存在。 (所以只有一次查找而不是两次)