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

Pag*_*Sun 44 c# multithreading dictionary locking

当页面初始化将从配置文件加载一些信息时,我们的网站有一个配置页面,如"config.aspx".为了缓存加载的信息,我们提供了一个工厂类,我们调用工厂的公共方法来获取页面加载时的配置实例.但有时当重新启动应用程序池时,我们在事件日志中发现了一些错误消息,如下所示:

Message: Object reference not set to an instance of an object.
Stack:   at System.Collections.Generic.Dictionary`2.Insert(TKey key, TValue value, Boolean add)
   at System.Collections.Generic.Dictionary`2.set_Item(TKey key, TValue value)
   at ObjectFactory.GetInstance(string key)
   at config.Page_Load(Object sender, EventArgs e)
   at System.Web.Util.CalliHelper.EventArgFunctionCaller(IntPtr fp, Object o, Object t, EventArgs e)
   at System.Web.Util.CalliEventHandlerDelegateProxy.Callback(Object sender, EventArgs e)
   at System.Web.UI.Control.OnLoad(EventArgs e)
   at System.Web.UI.Control.LoadRecursive()
   at System.Web.UI.Page.ProcessRequestMain(Boolean includeStagesBeforeAsyncPoint, Boolean 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;
    }
} 

我猜这个异常是由"Instances [key] = instance;"行抛出的,因为它是唯一可以调用set_Item字典方法的代码.但是如果"Instances"值为null,则NullReferenceException在调用TryGetValue方法时会抛出一个,而堆栈跟踪的顶部框架应该GetInstance不是Insert.有没有人知道字典在多线程场景中如何NullReferenceException调用set_Item方法?

Guf*_*ffa 52

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

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

编辑:
单独锁定访问,以便在执行(假设)耗时的加载时不在锁内:

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;
}
Run Code Online (Sandbox Code Playgroud)


jar*_*ics 35

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


Ian*_*emp 6

引用http://msdn.microsoft.com/en-us/library/xfhwa508.aspx(由我添加的重点):

" 线程安全

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

A Dictionary<(Of <(TKey, TValue>)>)可以同时支持多个读取器,只要不修改集合即可.即便如此,通过集合枚举本质上不是一个线程安全的过程.在枚举与写访问争用的极少数情况下,必须在整个枚举期间锁定该集合.为了允许多个线程访问集合进行读写,您必须实现自己的同步."