添加词典条目时出现异常

Ava*_*his 8 .net c# multithreading dictionary .net-4.0

我们看到在IIS 7服务器上运行的ASP.NET上下文中的以下代码块中发生此异常.

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)
Run Code Online (Sandbox Code Playgroud)

这是发生异常的代码:

[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;
    }
}
Run Code Online (Sandbox Code Playgroud)

Monitor.TryEnter应防止同时进入所述块的多个线程,代码正在检查字典,以确认它不包含被添加的关键.

关于如何发生这种情况的任何想法?

sll*_*sll 5

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

lock (localSerializers)
{
   if (!localSerializers.ContainsKey(sessionObjectName))                 
   {                     
      localSerializers.Add(
            sessionObjectName, 
            CreateSerializer(serializerType));                 
   } 
}
Run Code Online (Sandbox Code Playgroud)

编辑: 由于您没有在标签中指定您使用.NET 4,我建议使用 ConcurrentDictionary<TKey, TValue>


Monitor.Enter()方法:

使用C#try ... finally块(尝试...最后在Visual Basic中)以确保释放监视器,或使用C#lock语句(Visual Basic中的SyncLock语句),它将try和Exit方法包装在try ... finally块中


Joe*_*Joe 5

您的代码不是线程安全的.

  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;
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

来自评论:

如果我用ConcurrentDictionary实现它,并且每次只调用TryAdd(sessionObjectName,CreateSerializer(serializerType)).

答案不是每次调用TryAdd - 首先检查它是否在字典中,然后添加,如果不是.更好的选择可能是使用带参数GetOrAdd重载Func.

  • +1,因为这个答案完全描述了现有的问题,并建议如何解决不同的情况 (2认同)