C# Web应用Dictionary加锁方案咨询:自写加锁写法是否可行?
问题:Dictionary重复添加键报错,加锁方案是否可行?
我的C# Web应用运行时抛出错误:C# System.Collections.Generic.Dictionary2.Insert - An item with the same key has already been added`,排查后确定需要给代码加锁,我尝试了下面的加锁方式,请问这个方案是否可行?
原代码
if (keyVals == null) { keyVals = new Dictionary<string, string>(); } if (activity != null) { keyVals.Add(string.Format("{0}S1", prefix), test.GetType().FullName); keyVals.Add(string.Format("{0}S2", prefix), test.acode); keyVals.Add(string.Format("{0}S3", prefix), test.id.ToString()); keyVals.Add(string.Format("{0}S4", prefix), test.unme); } else { keyVals.Add("testtype", "N/A"); }
加锁后的代码
using (new Lock(myLockObject)) { if (keyVals == null) { keyVals = new Dictionary<string, string>(); } if (activity != null) { keyVals.Add(string.Format("{0}S1", prefix), test.GetType().FullName); keyVals.Add(string.Format("{0}S2", prefix), test.acode); keyVals.Add(string.Format("{0}S3", prefix), test.id.ToString()); keyVals.Add(string.Format("{0}S4", prefix), test.unme); } else { keyVals.Add("testtype", "N/A"); } }
回答
这个加锁方案能否生效取决于几个关键前提:
1. 锁对象myLockObject必须正确
myLockObject不能是局部变量,必须是所有并发线程能共享的对象:
- 如果
keyVals是类的实例字段,myLockObject应该是该类的私有实例字段(比如private readonly object _lockObj = new object();) - 如果
keyVals是静态字段,myLockObject必须是静态私有字段(比如private static readonly object _lockObj = new object();)
要是锁对象不对,每个线程用自己的锁,根本起不到互斥作用,并发问题还是会出现。
2. 所有对keyVals的操作都要加锁
Dictionary本身完全不是线程安全的,除了这段添加代码,其他任何读写keyVals的地方(比如遍历、修改、读取键值)都必须用同一个myLockObject加锁,否则依然会引发线程安全问题,比如读取时的集合修改异常,或者重复添加键。
3. 先排除单线程内的重复键问题
加锁只能解决并发导致的重复添加,如果单线程内多次执行这段代码,或者prefix重复导致键重复,加锁也没用。这种情况应该把Add换成TryAdd,或者先判断键是否存在:
// 示例:用TryAdd避免单线程内重复添加 if (activity != null) { keyVals.TryAdd(string.Format("{0}S1", prefix), test.GetType().FullName); // 其他字段同理 }
总结
如果满足「锁对象是共享的」+「所有keyVals操作都加锁」这两个条件,这个方案能解决并发导致的重复添加问题。但更推荐用.NET自带的ConcurrentDictionary,它本身是线程安全的,不需要手动加锁,用TryAdd或AddOrUpdate方法就能更可靠地处理并发场景。
内容的提问来源于stack exchange,提问作者Techno
相关产品推荐
相关产品推荐

