我遇到了一个非常奇怪的(对我来说......)例外。它很少发生,但确实......
我的类不是静态的,但只有一个静态属性:
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标记了它,因为它之前从未发生过,尽管代码没有改变。 任何的想法?感谢
答案 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)
;
也很棒。它给你价值,如果它存在。 (所以只有一次查找而不是两次)