我们看到在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)
这是发生异常的代码:
[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 应该阻止多个线程同时进入块,并且代码检查字典以验证它不包含正在添加的密钥。
关于如何发生这种情况的任何想法?
答案 0 :(得分:5)
尝试锁定localSerializers
而不是this
。顺便说一下,为什么你明确地使用Monitor?我看到的唯一原因是提供显然您没有使用的lock timeout,因此请简单地使用lock() statement来生成try / finally:
lock (localSerializers)
{
if (!localSerializers.ContainsKey(sessionObjectName))
{
localSerializers.Add(
sessionObjectName,
CreateSerializer(serializerType));
}
}
修改强>
由于您没有在标签中指定您使用.NET 4,我建议使用
ConcurrentDictionary<TKey, TValue>
使用C#try ... finally块(尝试...最后在Visual Basic中)来确保 您释放监视器,或使用C#锁定语句(SyncLock Visual Basic中的语句,它包含了Enter和Exit方法 试试......最后阻止
答案 1 :(得分:5)
您的代码不是线程安全的。
您正在锁定this
CredentialsSession
实例,但访问可由多个CredentialsSession
实例共享的静态字典。这解释了为什么您收到错误 - 两个不同的CredentialsSession
实例正在尝试同时写入字典。
即使您按照@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 - 先检查它是否在字典中,然后添加,如果不是。更好的选择可能是使用带有Func
参数的the GetOrAdd
overload。
答案 2 :(得分:1)
如果您使用的是.NET Framework 4或更高版本,我建议您改用ConcurrentDictionary
。 TryAdd
方法可以保护您免受此类情况的影响,而无需使用锁定来丢弃代码:
localSerializers.TryAdd(sessionObjectName, CreateSerializer(serializerType))
如果您担心在不需要时调用CreateSerializer
,则应改为使用AddOrUpdate
:
localSerializers.AddOrUpdate(
sessionObjectName,
key => CreateSerialzer(serializerType),
(key, value) => value);
这将确保仅在需要生成新值时(当需要将其添加到字典中时)才调用该方法。如果它已经存在,则该条目将使用现有值“更新”。