Подтвердить что ты не робот

Выбросить исключение NullReferenceException при вызове метода set_item объекта Dictionary в многопоточном сценарии

На нашем веб-сайте есть страница конфигурации, такая как "config.aspx", когда инициализация страницы загрузит некоторую информацию из файла конфигурации. Чтобы кэшировать загруженную информацию, мы предоставили класс factory, и мы вызываем общедоступный метод factory, чтобы получить экземпляр конфигурации при загрузке страницы. Но иногда, когда пул приложений перезагружается, мы обнаружили сообщение об ошибке в журнале событий, например:

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)

Класс factory реализует, как показано ниже:


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

Я предполагаю, что исключение было выбрано строкой "Экземпляры [ключ] = экземпляр", потому что это единственный код, который мог бы вызвать метод set_Item словаря. Но если значение "Экземпляры" равно NULL, при вызове метода TryGetValue будет выбрано значение NullReferenceException, а верхний фрейм stacktrace должен быть GetInstance, а не Insert. Кто-нибудь знает, как словарь может выбросить NullReferenceException при вызове метода set_Item в многопоточном сценарии?

4b9b3361

Ответ 1

Поскольку исключение происходит внутри кода 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;
}

Ответ 2

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

Ответ 3

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

" Безопасность потоков

Элементы public static (Shared in Visual Basic) этого типа являются потокобезопасными. Любые члены экземпляра не гарантируют безопасность потоков.

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

Ответ 4

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

ИЗМЕНИТЬ

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

Ответ 5

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