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

时间:2009-08-24 05:32:48

标签: 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

5 个答案:

答案 0 :(得分:50)

由于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;
}

答案 1 :(得分:31)

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

答案 2 :(得分:5)

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

线程安全

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

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

答案 3 :(得分:1)

我认为您的Instances Dictionary不是空的。您的异常来自Insert方法内部 - 意味着在运行时有一个Dictionary对象(另外,正如您所说,您之前已经有过TryGetValue个同一个引用) 可能是你的key是空的吗?

修改

刚检查过它 - TryGetValue在收到空键时抛出ArgumentNullException,因此使用null键插入。但是你在你的例子中使用了什么类?我使用了泛型IDictionary<string, string>,但我发现你使用的是非泛型的。它是您从DictionaryBase还是HashTable继承的课程?

答案 4 :(得分:1)

更好的解决方案是创建同步字典。这是一个适用于这种情况的工具。我认为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();
        }
    }
}