Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/296.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181

Warning: file_get_contents(/data/phpspider/zhask/data//catemap/6/multithreading/4.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 在多线程场景中调用Dictionary对象的set_item方法时引发NullReferenceException_C#_Multithreading_Dictionary_Locking - Fatal编程技术网

C# 在多线程场景中调用Dictionary对象的set_item方法时引发NullReferenceException

C# 在多线程场景中调用Dictionary对象的set_item方法时引发NullReferenceException,c#,multithreading,dictionary,locking,C#,Multithreading,Dictionary,Locking,我们的网站有一个配置页面,如“config.aspx”,当页面初始化时,将从配置文件加载一些信息。为了缓存加载的信息,我们提供了一个工厂类,并在加载页面时调用工厂的公共方法来获取配置实例。但有时在重新启动应用程序池时,我们会在事件日志中发现一些错误消息,如下所示: Message: Object reference not set to an instance of an object. Stack: at System.Collections.Generic.Dictionary`2.In

我们的网站有一个配置页面,如“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)
我猜异常是由“Instances[key]=instance;”行引发的,因为它是唯一可以调用字典的
set\u Item
方法的代码。但是如果“Instances”值为null,则在调用
TryGetValue
方法时会抛出
NullReferenceException
,stacktrace的顶部框架应该是
GetInstance
而不是
Insert
。有人知道在多线程场景中调用
set\u Item
方法时,字典如何抛出
NullReferenceException
吗?

我认为您的
实例
字典
不是空的。您的异常来自
Insert
方法内部-这意味着在运行时有一个
Dictionary
对象(同样,正如您所说,您以前在同一引用上已经有了
TryGetValue
) 您的
键是否为空

编辑
刚刚选中它-
TryGetValue
在接收到空键时抛出ArgumentNullException,使用空键插入也会抛出ArgumentNullException。但是您在示例中使用的是什么类?我使用了泛型的
IDictionary
,但我看到您使用的是非泛型的。它是从
DictionaryBase
继承的类还是从
HashTable
继承的类?

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

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

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

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;
}
引述(由我补充强调):

“线程安全

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


字典)
可以同时支持多个读取器,只要不修改集合。即使如此,通过集合进行枚举本质上也不是线程安全过程。在枚举与写访问争用的罕见情况下,必须在整个枚举过程中锁定集合。要允许要被多个线程访问以进行读写,您必须实现自己的同步。”

更好的解决方案是创建一个同步字典。在这种情况下,这是一个可行的方法。我认为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();
        }
    }
}
公共密封类同步字典
{
专用只读词典Dictionary=新词典();
private ReaderOnly ReaderWriterLockSlim readerWriterLock=新的ReaderWriterLockSlim();
公共TValue此[TKey]
{
得到
{
readerWriterLock.EnterReadLock();
尝试
{
返回这个。字典[键];
}
最后
{
readerWriterLock.exitradlock();
}
}
设置
{
readerWriterLock.EnterWriteLock();
尝试
{
this.dictionary[key]=值;
}
最后
{
readerWriterLock.ExitWriteLock();
}
}
}
公共bool TryGetValue(TKey键,out TValue值)
{
readerWriterLock.EnterReadLock();
尝试
{
返回this.dictionary.TryGetValue(key,out值);
}
最后
{
readerWriterLock.exitradlock();
}
}
}

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

是的,也许是这样(与Guffa所说的一样)-您至少需要锁定Insert行。您可能会有两个线程找不到同一个密钥,然后一个接一个地读取和插入它(理论上是17个线程),但实际的插入将同步。@诺姆:仅锁定插入并不能保证线程安全,对字典的所有访问都必须有锁。但是只对插入项进行锁定将确保不会同时插入两个对象。即使不同的线程正在插入所需的值,TryGetValue仍可能返回false,但不会出现两个线程同时尝试插入的情况,这可能导致异常。当我搜索“dictionary set\u Item NullReferenceException”等关键字时我发现有人在msdn文档“Dictionary.Item Property”提到:“在线程场景中,插入项可能会导致NullReferenceException,因为在插入调用期间可能添加或删除了项。如果您的应用程序使用多个线程,请确保在添加元素之前锁定字典。”但我仍然不知道如何在调试期间满足异常条件。链接是:Instants只是一个通用字典实例,TK
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();
        }
    }
}