在multithreading场景中调用Dictionary对象的set_item方法时抛出NullReferenceException

我们的网站有一个configuration页面,如“config.aspx”,当页面初始化时会从configuration文件中加载一些信息。 为了caching加载的信息,我们提供了一个工厂类,我们调用工厂的一个公共方法来获取页面加载时的configuration实例。 但有时候,当应用程序池重新启动时,我们在事件日志中发现了一些错误信息,例如:

消息:对象引用未设置为对象的实例。
堆栈:在System.Collections.Generic.Dictionary`2.Insert(TKey键,TValue值,布尔添加)
   在System.Collections.Generic.Dictionary`2.set_Item(TKey键,TValue值)
   在ObjectFactory.GetInstance(string键)
   在config.Page_Load(对象发件人,EventArgs e)
   在System.Web.Util.CalliHelper.EventArgFunctionCaller(IntPtr fp,Object o,Object t,EventArgs e)
   在System.Web.Util.CalliEventHandlerDelegateProxy.Callback(对象发件人,EventArgs e)
   在System.Web.UI.Control.OnLoad(EventArgs e)
   在System.Web.UI.Control.LoadRecursive()
   在System.Web.UI.Page.ProcessRequestMain(布尔includeStagesBeforeAsyncPoint,布尔includeStagesAfterAsyncPoint)

工厂类实现如下:

public static class ObjectFactory { private static object _InternalSyncObject; private static Dictionary _Instances; private static object InternalSyncObject { get { if (_InternalSyncObject == null) { var @object = new object(); Interlocked.CompareExchange(ref _InternalSyncObject, @object, null); } return _InternalSyncObject; } } private static Dictionary Instances { get { if (_Instances == null) { lock (InternalSyncObject) { if (_Instances == null) { _Instances = new Dictionary(); } } } return _Instances; } } private static object LoadInstance(string key) { object obj = null; // some statements to load an specific instance from a configuration file. return obj; } public static object GetInstance(string key) { object instance; if (false == Instances.TryGetValue(key, out instance)) { instance = LoadInstance(key); Instances[key] = instance; } return instance; } } 

我猜这个exception是由“Instances [key] = instance;”行引发的,因为它是唯一可以调用字典的set_Item方法的代码。 但是,如果“实例”值为空,则在调用TryGetValue方法时将抛出NullReferenceException ,并且GetInstance的顶部框架应为GetInstance而不是Insert 。 有谁知道如何在multithreading场景中调用set_Item方法时字典可以抛出NullReferenceException

由于Dictionary代码内部发生exception,这意味着您正在同时从多个线程访问同一个Dictionary实例。

您需要同步GetInstance方法中的代码,以便一次只有一个线程访问Dictionary

编辑:
分开locking访问,以便在进行(假定)耗时的加载时不会出现locking:

 private static object _sync = new object(); public static object GetInstance(string key) { object instance = null; bool found; lock (_sync) { found = Instances.TryGetValue(key, out instance); } if (!found) { instance = LoadInstance(key); lock (_sync) { object current; if (Instances.TryGetValue(key, out current)) { // some other thread already loaded the object, so we use that instead instance = current; } else { Instances[key] = instance; } } } return instance; } 

从.Net 4开始,你有一个线程安全的字典ConcurrentDictionary ,不再需要“手动”同步。

引用http://msdn.microsoft.com/en-us/library/xfhwa508.aspx (强调加我):

线程安全

此types的公共静态(在Visual Basic中为Shared)成员是线程安全的。 任何实例成员不保证是线程安全的。

一个Dictionary<(Of <(TKey, TValue>)>)可以同时支持多个阅读器,只要该集合没有被修改。 即便如此,通过集合枚举本质上不是一个线程安全的过程。 在枚举与写入访问竞争的罕见情况下,集合在整个枚举期间必须被locking。 为了允许多个线程读取和写入集合,必须实现自己的同步 。“

我认为你的Instances Dictionary不是null。 你的exception来自于Insert方法 – 这意味着在运行时有一个Dictionary对象(另外,正如你所说的,在同一个引用之前,你已经有了TryGetValue )是不是你的key是空的?

编辑

只是检查它 – TryGetValue当它收到一个空键时抛出ArgumentNullException,并且用一个空键插入。 但是在你的例子中你使用了什么类? 我使用了genericsIDictionary<string, string> ,但是我看到你正在使用一个非generics的。 它是从DictionaryBase还是HashTableinheritance的类?

更好的解决scheme是创build一个同步字典。 这是一个能在这种情况下工作的人。 ReaderWriterLockSlim我认为是在这种情况下使用的最好的同步对象。 写字典将是非常罕见的。 大部分时间钥匙将在字典中。 我没有实现字典中的所有方法,只是在这种情况下使用的方法,所以它不是一个完整的同步字典。

 public sealed class SynchronizedDictionary<TKey, TValue> { private readonly Dictionary<TKey, TValue> dictionary = new Dictionary<TKey, TValue>(); private readonly ReaderWriterLockSlim readerWriterLock = new ReaderWriterLockSlim(); public TValue this[TKey key] { get { readerWriterLock.EnterReadLock(); try { return this.dictionary[key]; } finally { readerWriterLock.ExitReadLock(); } } set { readerWriterLock.EnterWriteLock(); try { this.dictionary[key] = value; } finally { readerWriterLock.ExitWriteLock(); } } } public bool TryGetValue(TKey key, out TValue value) { readerWriterLock.EnterReadLock(); try { return this.dictionary.TryGetValue(key, out value); } finally { readerWriterLock.ExitReadLock(); } } }