Создайте исключение NullReferenceException при вызове метода set_item объекта Dictionary в сценарии с многопоточностью - PullRequest
42 голосов
/ 24 августа 2009

На нашем веб-сайте есть страница конфигурации, такая как «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 метод словаря. Но если значение «Экземпляры» равно нулю, при вызове метода TryGetValue оно выдает NullReferenceException, а верхний кадр трассировки стека должен быть GetInstance, а не Insert. Кто-нибудь знает, как словарь может выдавать NullReferenceException при вызове метода set_Item в многопоточном сценарии?

Ответы [ 5 ]

51 голосов
/ 24 августа 2009

Поскольку внутреннее исключение происходит в коде Dictionary, это означает, что вы обращаетесь к одному и тому же экземпляру Dictionary из более чем одного потока одновременно.

Вам необходимо синхронизировать код в методе GetInstance, чтобы только один поток одновременно обращался к Dictionary.

Edit:
Блокируйте доступы по отдельности, чтобы вы не находились внутри блокировки при выполнении (предположительно) длительной загрузки:

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;
}
32 голосов
/ 10 ноября 2011

Начиная с .Net 4 у вас есть ConcurrentDictionary , который является потокобезопасным словарем, больше нет необходимости в "ручной" синхронизации.

5 голосов
/ 24 августа 2009

Цитировать http://msdn.microsoft.com/en-us/library/xfhwa508.aspx (выделено мной):

" Резьба безопасности

Открытые статические (Shared в Visual Basic) члены этого типа являются поточно-ориентированными. Ни один из членов экземпляра не гарантированно является потокобезопасным.

A Dictionary<(Of <(TKey, TValue>)>) может поддерживать несколько считывателей одновременно, если коллекция не изменена. Тем не менее, перечисление в коллекции по сути не является потокобезопасной процедурой. В редком случае, когда перечисление конкурирует с доступом для записи, коллекция должна быть заблокирована в течение всего перечисления. Чтобы разрешить доступ к коллекции из нескольких потоков для чтения и записи, необходимо реализовать собственную синхронизацию . "

1 голос
/ 26 августа 2009

Лучшим решением было бы создать синхронизированный словарь. Вот тот, который будет работать в этой ситуации. Я думаю, что 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();
        }
    }
}
1 голос
/ 24 августа 2009

Я думаю, что ваш Instances Dictionary не равен нулю. Ваше исключение происходит изнутри метода Insert - это означает, что во время выполнения существует объект Dictionary (Кроме того, как вы уже сказали, у вас уже было TryGetValue по той же ссылке) Может ли быть так, что ваш key равен нулю?

EDIT

Только что проверил - TryGetValue выдает ArgumentNullException, когда получает нулевой ключ, и вставляет с нулевым ключом. Но какой класс вы используете в своем примере? Я использовал общий IDictionary<string, string>, но я вижу, что вы используете не универсальный. Это класс, который вы унаследовали от DictionaryBase или, может быть, HashTable?

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...