C# 二级ConcurrentDictionary随主字典自动更新导致值对比异常问题
引用未做深拷贝,全程和主字典共享同一内存实例
Var属于引用类型,在initSecondaryDictionary方法中直接写ValueDTOToAdd.EligibleVar = variable;,本质是将主字典中Var对象的内存引用拷贝到二级字典的属性中。此时二级字典存储的EligibleVar和主字典中对应的Var是堆上的同一个实例,主字典更新该实例的字段值时,二级字典通过同一引用读取的内容会同步变化,永远无法出现值不一致的情况。
之前尝试“创建新元素而非直接传递主字典引用”未生效,是因为仅创建了外层的ValueDTO容器,容器内部持有的EligibleVar、ContextVars列表元素依然是主字典对象的原引用,没有对字段做逐字段的深拷贝,相当于仅更换了外层包装,内部依然和主字典完全绑定。
使用ConcurrentDictionary也无法解决该问题:ConcurrentDictionary仅保证字典自身增删改查操作的线程安全,不会对存储的元素做值拷贝,存入什么引用就保留什么引用,自然会跟随原对象的变化同步更新。
补充说明: 你代码中的锁逻辑完全无效——所有MainLock、secondaryLock都是方法内部临时实例化的局部对象,每次执行初始化、每轮轮询循环都会生成全新的锁实例,不同线程的lock操作针对的都是不同对象,根本不会产生锁争抢,完全起不到同步保护作用,属于多线程锁实现的典型误区。
轮询的对比逻辑从设计上就无法触发差值分支:从二级字典取出的variableInSecondary本身就是主字典Var对象的原引用,之后又通过varID调用getVarFromMainDictionary拿到同一个主字典Var实例,两个引用指向同一块内存,读取的int值必然100%相等,永远进不去if (valueInMain != valueInSecondary)判断是必然结果。
- 初始化阶段生成完全独立的快照:
- 禁止直接将主字典的Var实例赋值给二级字典属性,针对每个符合条件的Var,逐字段拷贝所有需要对比的属性值,生成全新的、堆地址独立的Var实例存入二级字典,确保与主字典原对象无任何引用关联。
- 针对ContextVars列表,除了创建独立的List容器,还要对列表内每个Var的字段值做逐字段拷贝生成独立新实例,不能直接存入主字典返回的Var引用。
- 重构锁实现:
- 将锁对象替换为类级别的静态只读共享实例,例如:
private static readonly object _mainDictLock = new object(); private static readonly object _secondaryDictLock = new object(); - 所有读写主字典的操作统一lock
_mainDictLock,所有读写二级字典的操作统一lock_secondaryDictLock,彻底替换原有局部临时锁对象。
- 将锁对象替换为类级别的静态只读共享实例,例如:
- 调整对比逻辑:
- 比对时,一侧取二级字典存储的初始化快照值,一侧取主字典的当前最新值做对比。此时两个值为完全独立的实例,主字典的更新不会影响二级字典存储的快照值,差值判断分支即可正常触发。
内容的提问来源于stack exchange,提问作者Aaron Parrilla

