with threading thread lock example español ejemplo c# multithreading dictionary locking

c# - lock - Lanzar una NullReferenceException al llamar al método set_item de un objeto de diccionario en un escenario de multi-threading



system threading thread c# (5)

Nuestro sitio web tiene una página de configuración como "config.aspx", cuando la inicialización de la página cargará información de un archivo de configuración. Para almacenar en caché la información cargada, proporcionamos una clase de fábrica y llamamos a un método público de la fábrica para obtener la instancia de configuración cuando se carga la página. Pero a veces, cuando se reinicia el grupo de aplicaciones, encontramos algunos mensajes de error en el registro de eventos, como a continuación:

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)

La clase de fábrica se implementa como a continuación:

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

Supongo que la excepción fue lanzada por la línea "Instances [key] = instance;", porque es el único código que podría llamar set_Item método set_Item de un diccionario. Pero si el valor "Instancias" es nulo, arrojará una NullReferenceException cuando llame al método TryGetValue y el marco superior de stacktrace debe ser GetInstance no Insert . ¿Alguien sabe cómo el diccionario podría arrojar una NullReferenceException cuando llame al método set_Item en el escenario multi-threading?


A partir de .Net 4 tiene ConcurrentDictionary que es un diccionario seguro para hilos, no necesita más sincronización "manual".


Como la excepción se produce internamente en el código del Dictionary , significa que está accediendo a la misma instancia de Dictionary de más de un hilo al mismo tiempo.

Necesita sincronizar el código en el método GetInstance para que solo un hilo por vez GetInstance acceso al Dictionary .

Editar:
Bloquee los accesos por separado, para que no se encuentre dentro de un bloqueo mientras realiza la carga (supuestamente) que consume mucho tiempo:

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


Creo que su Dictionary Instances no es nulo. Su excepción es desde dentro del método Insert , lo que significa que hay un objeto Dictionary en tiempo de ejecución (Además, como ya ha dicho, ya ha tenido TryGetValue antes, en la misma referencia) ¿Podría ser que su key sea ​​nula?

EDITAR

Acabo de comprobarlo: TryGetValue arroja ArgumentNullException cuando recibe una clave nula, y también lo hace con una clave nula. Pero, ¿qué clase estás usando en tu ejemplo? Utilicé el IDictionary<string, string> genérico IDictionary<string, string> , pero veo que está utilizando uno no genérico. ¿Es una clase que has heredado de DictionaryBase o quizás HashTable ?


Para citar http://msdn.microsoft.com/en-us/library/xfhwa508.aspx (énfasis agregado por mí):

" Seguridad de subprocesos

Los miembros públicos estáticos (Shared en Visual Basic) de este tipo son seguros para subprocesos. No se garantiza que ningún miembro de instancia sea seguro para subprocesos.

Un Dictionary<(Of <(TKey, TValue>)>) puede admitir varios lectores al mismo tiempo, siempre que la colección no se modifique. Aun así, enumerar a través de una colección no es intrínsecamente un procedimiento seguro para subprocesos. En el raro caso en que una enumeración contenga accesos de escritura, la colección debe estar bloqueada durante toda la enumeración. Para permitir que la colección sea accedida por múltiples hilos para lectura y escritura, debe implementar su propia sincronización ".


Una mejor solución sería crear un diccionario sincronizado. Aquí hay uno que funcionará en esta situación. El ReaderWriterLockSlim creo que es el mejor objeto de sincronización para usar en esta situación. La escritura en el diccionario será bastante rara. La mayoría de las veces la clave estará en el diccionario. No implementé todos los métodos del diccionario, solo los que se usaron en este caso, por lo que no es un diccionario completo sincronizado.

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