2011-11-16 6 views
8

Estamos viendo esta excepción en el siguiente bloque de código en un contexto ASP.NET que se ejecuta en un servidor IIS 7.Excepción al agregar la entrada del diccionario

1) Exception Information 
********************************************* 
Exception Type: System.Exception 
Message: Exception Caught in Application_Error event 
Error in: InitializationStatus.aspx 
Error Message:An item with the same key has already been added. 
Stack Trace: at 
System.Collections.Generic.Dictionary`2.Insert(TKey key, TValue value, Boolean add) 
at CredentialsSession.GetXmlSerializer(Type serializerType) 

Este es el código que la excepción se está produciendo en:

[Serializable()] 
public class CredentialsSession 
{ 
    private static Dictionary<string, System.Xml.Serialization.XmlSerializer> localSerializers = new Dictionary<string, XmlSerializer>(); 

    private System.Xml.Serialization.XmlSerializer GetXmlSerializer(Type serializerType) 
    { 
     string sessionObjectName = serializerType.ToString() + ".Serializer"; 

     if (Monitor.TryEnter(this)) 
     { 
      try 
      { 
       if (!localSerializers.ContainsKey(sessionObjectName)) 
       { 
        localSerializers.Add(sessionObjectName, CreateSerializer(serializerType)); 
       } 
      } 
      finally 
      { 
       Monitor.Exit(this); 
      } 
     } 
     return localSerializers[sessionObjectName]; 
    } 

    private System.Xml.Serialization.XmlSerializer CreateSerializer(Type serializerType) 
    { 
     XmlAttributes xmlAttributes = GetXmlOverrides(); 

     XmlAttributeOverrides xmlOverrides = new XmlAttributeOverrides(); 
     xmlOverrides.Add(typeof(ElementBase), "Elements", xmlAttributes); 

     System.Xml.Serialization.XmlSerializer serializer = 
      new System.Xml.Serialization.XmlSerializer(serializerType, xmlOverrides); 

     return serializer; 
    } 
} 

El Monitor.TryEnter debería ser la prevención de múltiples hilos de entrar en el bloque de forma simultánea, y el código está comprobando el diccionario para verifique que no contenga la clave que se está agregando.

¿Alguna idea sobre cómo podría suceder esto?

+0

+1, para la pregunta que explica cómo encontrar un duplicado en el Diccionario. –

Respuesta

5

Su código no es seguro para subprocesos.

  1. Estás bloqueo en this, una instancia CredentialsSession, pero el acceso a un diccionario estático que puede ser compartida por múltiples CredentialsSession casos. Esto explica por qué está recibiendo el error: dos instancias diferentes de CredentialsSession intentan escribir en el diccionario al mismo tiempo.

  2. Aunque cambie este para bloquear en un campo estático como se sugiere en la respuesta de @ SLL, no son hilos de proceso seguro, porque no se está fijando al leer el diccionario. Necesita un ReaderWriterLock o ReaderWriterLockSlim para permitir de manera eficiente varios lectores y un solo escritor.

    Por lo tanto, probablemente deba usar un diccionario seguro para subprocesos. ConcurrentDictionary como han dicho otros si está utilizando .NET 4.0. De lo contrario, debe implementar el suyo propio o utilizar una implementación existente, como http://devplanet.com/blogs/brianr/archive/2008/09/26/thread-safe-dictionary-in-net.aspx.

Sus comentarios sugieren que desea evitar llamar CreateSerializer para el mismo tipo varias veces. No sé por qué, porque es probable que el beneficio de rendimiento sea insignificante, ya que es probable que la contención sea rara y no puede exceder una vez para cada tipo durante la vida de la aplicación.

Pero si realmente quieres esto, puede hacerlo de la siguiente manera:

var value; 
if (!dictionary.TryGetValue(key, out value)) 
{ 
    lock(dictionary) 
    { 
     if(!dictionary.TryGetValue(key, out value)) 
     { 
      value = CreateSerializer(...); 
      dictionary[key] = value; 
     } 
    } 
} 

De Comentario:

si puedo aplicar esto con ConcurrentDictionary y simplemente llamo TryAdd (sessionObjectName, CreateSerializer (serializerType)) cada vez.

La respuesta no es llamar a TryAdd cada vez: primero compruebe si está en el diccionario, y luego agregue si no lo está. Una mejor alternativa podría ser usar the GetOrAdd overload que tome un argumento Func.

+0

¡No había notado que el Diccionario es estático! Eso explica mucho, ¡gracias! Estoy volviendo a implementar esto con ConcurrentDictionary. – Avalanchis

+0

Suponiendo que GetXmlSerializer recibe una llamada frecuente para recuperar valores del diccionario, si implemento esto con ConcurrentDictionary y simplemente llame a TryAdd (sessionObjectName, CreateSerializer (serializerType)) cada vez, se llamará CreateSerializer cada vez que se llame a GetXmlSerializer. Parece que me gustaría evitar esto. Una vez que el diccionario se rellena para cada valor clave, no hay razón para llamar a CreateSerializer.¿Correcto? – Avalanchis

+2

+1 porque esta respuesta describe completamente el problema existente y sugiere cómo resolverlo para diferentes casos – sll

5

Pruebe el bloqueo en localSerializers en lugar de this. Por cierto, ¿por qué estás usando el Monitor explícitamente? Sólo una de las razones que veo es proporcionar lock timeout que obviamente no está utilizando, a fin de utilizar simplemente lock() statement vez esto generaría try/finally así:

lock (localSerializers) 
{ 
    if (!localSerializers.ContainsKey(sessionObjectName))     
    {      
     localSerializers.Add(
      sessionObjectName, 
      CreateSerializer(serializerType));     
    } 
} 

EDIT: Puesto que no se ha especificado en las etiquetas que está utilizando .NET 4 se recomienda usar ConcurrentDictionary<TKey, TValue>


Monitor.Enter() Method:

Utilice un bloque C# try ... finally (Pruebe ... Finalmente en Visual Basic) para asegurar que libera el monitor, o use la instrucción de bloqueo C# (instrucción SyncLock en Visual Basic), que ajusta los métodos Entrar y Salir en un trato ... finally

+0

¡Gracias por la respuesta! Originalmente no es mi código, por lo que no puedo proporcionar una respuesta sobre por qué está usando el Monitor explícitamente. Tiene mucho sentido bloquear el recurso que se está protegiendo en lugar de esto. Voy a intentar esto, gracias! – Avalanchis

+0

@Avalanchis: vea la parte EDITAR de la respuesta actualizada, también he agregado la etiqueta .NET 4 para su pregunta, es bastante importante – sll

+0

Me gusta la idea de usar ConcurrentDictionary, pero me preocupan las llamadas innecesarias a CreateSerializer. Parece que todavía necesito llamar a ContainsKey para ver si la clave existe antes de llamar a TryAdd si quiero evitar llamar a CreateSerializer cada vez. ¿Correcto? – Avalanchis

1

Si usted está en .NET Framework 4 o posterior, sugeriría que utilice un ConcurrentDictionary lugar. El método TryAdd mantiene a salvo de este tipo de escenario, sin la necesidad de la basura su código con cerraduras:

localSerializers.TryAdd(sessionObjectName, CreateSerializer(serializerType)) 

Si usted está preocupado por CreateSerializer que se invoca cuando no se necesita, en su lugar debe usar AddOrUpdate:

localSerializers.AddOrUpdate(
    sessionObjectName, 
    key => CreateSerialzer(serializerType), 
    (key, value) => value); 

Esto asegurará que el método se invoque solo cuando necesite generar un nuevo valor (cuando deba agregarse al diccionario). Si ya está presente, la entrada se "actualizará" con el valor ya existente.

+0

Estamos usando .NET 4. ¿Tendría aún sentido verificar si el ConcurrentDictionary contiene la clave que debe agregarse primero? Me preocupa que TryAdd pueda llamar innecesariamente a CreateSerializer si la clave ya existe. – Avalanchis

+0

@Avalanchis: vea la respuesta actualizada. –

Cuestiones relacionadas