Исключение при добавлении словарной записи - PullRequest
8 голосов
/ 16 ноября 2011

Мы видим, что это исключение происходит в следующем блоке кода в контексте ASP.NET, который выполняется на сервере IIS 7.

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)

Это код, в котором происходит исключение:

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

Monitor.TryEnter должен предотвращать одновременный вход нескольких потоков в блок, и код проверяет словарь, чтобы убедиться, что он не содержит ключадобавлено.

Есть идеи, как это может произойти?

Ответы [ 3 ]

5 голосов
/ 16 ноября 2011

Ваш код не является потокобезопасным.

  1. Вы блокируете this, экземпляр CredentialsSession, но получаете доступ к статическому словарю, который может использоваться несколькими экземплярами CredentialsSession. Это объясняет, почему вы получаете ошибку - два разных экземпляра CredentialsSession пытаются записать в словарь одновременно.

  2. Даже если вы измените это, чтобы заблокировать статическое поле, как предложено в ответе @ sll, вы не поточно-ориентированы, потому что вы не блокируете при чтении словаря. Вам нужен ReaderWriterLock или ReaderWriterLockSlim, чтобы эффективно разрешить работу нескольких читателей и одного писателя.

    Поэтому вам, вероятно, следует использовать многопоточный словарь. ConcurrentDictionary как говорили другие, если вы используете .NET 4.0. Если нет, вы должны реализовать свою собственную или использовать существующую реализацию, такую ​​как http://devplanet.com/blogs/brianr/archive/2008/09/26/thread-safe-dictionary-in-net.aspx.

Ваши комментарии предполагают, что вы хотите избегать многократного вызова CreateSerializer для одного и того же типа. Я не знаю почему, потому что выигрыш в производительности, вероятно, будет незначительным, поскольку конфликты, вероятно, будут редкими и не могут превышать один раз для каждого типа в течение срока службы приложения.

Но если вы действительно этого хотите, вы можете сделать это следующим образом:

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

Из комментария:

если я реализую это с ConcurrentDictionary и просто каждый раз вызываю TryAdd (sessionObjectName, CreateSerializer (serializerType)).

Ответ не в том, чтобы каждый раз вызывать TryAdd - сначала проверьте, есть ли он в словаре, а затем добавьте, если это не так. Лучшей альтернативой может быть использование перегрузки GetOrAdd , которая принимает аргумент Func.

5 голосов
/ 16 ноября 2011

Попробуйте заблокировать localSerializers вместо this. Кстати, почему вы используете монитор явно? Я вижу только одну причину - предоставить тайм-аут блокировки , который, очевидно, вы не используете, поэтому просто используйте оператор lock () , вместо этого также будет генерироваться try / finally:

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

EDIT: Поскольку вы не указали в тегах, которые вы используете .NET 4, я бы предложил использовать ConcurrentDictionary<TKey, TValue>


Метод Monitor.Enter () :

Используйте C # try ... finally блок (Try ... Наконец в Visual Basic), чтобы убедиться, освободить монитор или использовать оператор блокировки C # (SyncLock оператор в Visual Basic), который оборачивает методы Enter и Exit в попытка ... наконец-то блок

1 голос
/ 16 ноября 2011

Если вы работаете в .NET Framework 4 или более поздней версии, я бы предложил вместо этого использовать ConcurrentDictionary. Метод TryAdd защищает вас от такого сценария, без необходимости засорять ваш код блокировками:

localSerializers.TryAdd(sessionObjectName, CreateSerializer(serializerType))

Если вы беспокоитесь о том, что CreateSerializer может быть вызвано, когда оно не нужно, вместо этого вы должны использовать AddOrUpdate:

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

Это обеспечит вызов метода только тогда, когда вам нужно создать новое значение (когда его нужно добавить в словарь). Если он уже присутствует, запись будет «обновлена» уже существующим значением.

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