默认构造函数后出现空引用异常
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>
.
我遇到了一个非常奇怪的(对我来说..)异常。它很少发生,但确实...
我的 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>
.