Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Exception when adding Dictionary entry

We're seeing this exception occur in the following block of code in an ASP.NET context which is running on an IIS 7 server.

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)

This is the code that the exception is occuring in:

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

The Monitor.TryEnter should be preventing multiple threads from entering the block simultaneously, and the code is checking the Dictionary to verify that it does not contain the key that is being added.

Any ideas on how this could happen?

like image 849
Avalanchis Avatar asked Nov 16 '11 11:11

Avalanchis


People also ask

How do you avoid KeyError in the dictionary?

Avoiding KeyError when accessing Dictionary Key We can avoid KeyError by using get() function to access the key value. If the key is missing, None is returned. We can also specify a default value to return when the key is missing.

How do you fix KeyError?

How to Fix KeyError in Python. To avoid the KeyError in Python, keys in a dictionary should be checked before using them to retrieve items. This will help ensure that the key exists in the dictionary and is only used if it does, thereby avoiding the KeyError . This can be done using the in keyword.

Why am I getting a KeyError in Python?

A Python KeyError exception is what is raised when you try to access a key that isn't in a dictionary ( dict ). Python's official documentation says that the KeyError is raised when a mapping key is accessed and isn't found in the mapping. A mapping is a data structure that maps one set of values to another.

How do you add to a dictionary in C#?

Add() Method is used to add a specified key and value to the dictionary. Syntax: public void Add (TKey key, TValue value);


2 Answers

Your code is not thread-safe.

  1. You're locking on this, a CredentialsSession instance, but accessing a static dictionary which can be shared by multiple CredentialsSession instances. This explains why you're getting the error - two different CredentialsSession instances are attempting to write to the dictionary concurrently.

  2. Even if you change this to lock on a static field as suggested in @sll's answer, you aren't thread-safe, because you aren't locking when reading the dictionary. You need a ReaderWriterLock or ReaderWriterLockSlim to efficiently allow multiple readers and a single writer.

    Therefore you should probably use a thread-safe dictionary. ConcurrentDictionary as others have said if you're using .NET 4.0. If not you should implement your own, or use an existing implementation such as http://devplanet.com/blogs/brianr/archive/2008/09/26/thread-safe-dictionary-in-net.aspx.

Your comments suggest you want to avoid calling CreateSerializer for the same type multiple times. I don't know why, because the performance benefit is likely to be negligible, since contention is likely to be rare and can't exceed once for each type during the lifetime of the application.

But if you really want this, you can do it as follows:

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

From comment:

if I implement this with ConcurrentDictionary and simply call TryAdd(sessionObjectName, CreateSerializer(serializerType)) every time.

The answer is not to call TryAdd every time - first check if it's in the dictionary, then add if it isn't. A better alternative might be to use the GetOrAdd overload that takes a Func argument.

like image 170
Joe Avatar answered Sep 29 '22 04:09

Joe


Try out locking on localSerializers rather that this. BTW, why you are using Monitor explicitly? Only one reason I see is to provide lock timeout which obviously you are not using, so use simply lock() statement instead this would generate try/finally as well:

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

EDIT: Since you've not specified in tags that you're using .NET 4 I would suggest using ConcurrentDictionary<TKey, TValue>


Monitor.Enter() Method:

Use a C# try…finally block (Try…Finally in Visual Basic) to ensure that you release the monitor, or use the C# lock statement (SyncLock statement in Visual Basic), which wraps the Enter and Exit methods in a try…finally block

like image 24
sll Avatar answered Sep 29 '22 02:09

sll