添加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应该防止多个线程同时进入块,并且代码正在检查字典以验证它不包含正在添加的键。
你知道这是怎么发生的吗?
你的代码不是线程安全的。
-
你正在锁定
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
参数的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);
这将确保仅在需要生成新值(当需要将其添加到字典中时)时才调用该方法。如果它已经存在,条目将使用已经存在的值"更新"。