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应防止同时进入所述块的多个线程,代码正在检查字典,以确认它不包含被添加的关键.
关于如何发生这种情况的任何想法?
尝试锁定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>
使用C#try ... finally块(尝试...最后在Visual Basic中)以确保释放监视器,或使用C#lock语句(Visual Basic中的SyncLock语句),它将try和Exit方法包装在try ... finally块中
您的代码不是线程安全的.
您正在锁定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;
}
}
}
Run Code Online (Sandbox Code Playgroud)
来自评论:
如果我用ConcurrentDictionary实现它,并且每次只调用TryAdd(sessionObjectName,CreateSerializer(serializerType)).
答案不是每次调用TryAdd - 首先检查它是否在字典中,然后添加,如果不是.更好的选择可能是使用带参数的GetOrAdd
重载Func
.