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

Null reference Exception after default constructor

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

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

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" 或即时 window 时,任何地方(字典或 resetEvent)都没有 null。

p.s - 我将它标记为 visual studio 2017 年,因为它以前从未发生在我身上,尽管代码没有改变。 任何的想法?谢谢

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

类似于:

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 也很棒。它给了你价值,如果它存在的话。 (所以只有一次查找而不是两次)

如果您从多个线程调用 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>.