添加Dictionary条目时出现异常

本文关键字:异常 Dictionary 添加 | 更新日期: 2023-09-27 18:14:43

我们看到这个异常发生在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;
    }
}

监视器。TryEnter应该防止多个线程同时进入块,并且代码正在检查字典以验证它不包含正在添加的键。

你知道这是怎么发生的吗?

添加Dictionary条目时出现异常

你的代码不是线程安全的。

  1. 你正在锁定this,一个CredentialsSession实例,但访问一个静态字典,可以由多个CredentialsSession实例共享。这就解释了为什么会出现这个错误——两个不同的CredentialsSession实例试图同时写入字典。

  2. 即使您将此更改为@sll回答中建议的锁定静态字段,您也不是线程安全的,因为您在读取字典时不会锁定。您需要ReaderWriterLockReaderWriterLockSlim来有效地支持多个读取器和单个写入器。

    因此,您可能应该使用线程安全的字典。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——首先检查它是否在字典中,如果不在,然后添加。更好的替代方法可能是使用接受Func参数的GetOrAdd重载。

尝试在localSerializers而不是this上锁定。顺便说一句,为什么要明确地使用Monitor ?我看到的唯一一个原因是提供锁定超时,显然你没有使用,所以使用简单的lock()语句来代替,这将生成try/finally:

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

由于您没有在标签中指定使用。net 4,我建议使用ConcurrentDictionary<TKey, TValue>


Monitor.Enter()方法:

使用c# try…finally块(在Visual Basic中try…finally)来确保释放监视器,或者使用c#的锁语句(SyncLock语句),它将Enter和Exit方法封装在try…finally块

如果你使用。net Framework 4或更高版本,我建议你使用ConcurrentDictionary代替。TryAdd方法可以避免这种情况,而不需要在代码中添加锁:

localSerializers.TryAdd(sessionObjectName, CreateSerializer(serializerType))

如果你担心CreateSerializer在不需要的时候被调用,你应该使用AddOrUpdate:

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

这将确保仅在需要生成新值(当需要将其添加到字典中时)时才调用该方法。如果它已经存在,条目将使用已经存在的值"更新"。