在C# ConcurrentDictionary的键更新中操作其他键是否存在并发问题?
ConcurrentDictionary值工厂中操作其他键的并发风险疑问
问题描述
我在C# 7环境下使用ConcurrentDictionary的GetOrAdd方法,采用Tuple<string,string>作为组合键(例如"Key1,Part-key1")。由于从远程加载数据时会获取完整文档,但字典使用者仅需要部分键对应的数据,因此我在值工厂中获取"Key1"下的完整对象后,会预加载其余"part-keyN"对应的键值对,供后续调用使用。
我的疑问是:在ConcurrentDictionary的键更新过程中操作其他键,是否可能出现死锁或类似的并发问题?
代码示例
var cd = new ConcurrentDictionary<Tuple<string,string>,Task<string>>(); cd.GetOrAdd(Tuple.Create("Key1","Part1"), async (k)=>{ var fullObject = await retrieveObject(k.Item1); foreach(var prop in fullObject.Properties){ cd.TryAdd(Tuple.Create(k.Item1,prop.Key),prop.Value); } return fullObject.Properties.Find(prop => prop.Key == k.Item2).Value; });
注:组合键指存入字典键字段的Tuple。
参考:值工厂
回答
核心结论
不会出现死锁,但存在竞态条件和其他潜在问题:
死锁风险排除:
ConcurrentDictionary采用基于键哈希的分段锁机制,操作不同键时会获取不同的锁段,不存在循环等待的死锁场景。值工厂内调用TryAdd操作其他键,与当前GetOrAdd占用的锁段无冲突,因此不会触发死锁。竞态条件问题
- 当多个线程同时请求同一主键下的不同子键(比如同时请求
("Key1","Part1")和("Key1","Part2")),会触发多个值工厂并行执行,导致多次调用retrieveObject("Key1"),重复加载远程数据,造成资源浪费。 - 第一个完成加载的线程会预加载所有子键,但后续线程的
TryAdd会因键已存在而失败,最终结果一致,但重复加载的问题需要优化。
- 当多个线程同时请求同一主键下的不同子键(比如同时请求
异步值工厂的陷阱
- 代码中值工厂为
async方法,返回Task<string>,GetOrAdd会直接将该Task存入字典而非等待任务完成。后续线程请求同一键时会拿到同一个Task,这部分是安全的,但需注意:若retrieveObject抛出异常,异常会被封装在Task中,后续访问该键时需处理异常。 - 代码存在类型不匹配错误:字典类型是
ConcurrentDictionary<Tuple<string,string>,Task<string>>,但TryAdd存入的是prop.Value(假设为string),需改为Task.FromResult(prop.Value)才能编译通过。
- 代码中值工厂为
优化建议
- 针对同一主键的子键请求,共享同一个加载任务以避免重复远程调用:可先维护一个
ConcurrentDictionary<string, Task<FullObject>>,当请求子键时,先获取主键对应的加载任务,等待完成后提取对应子键的值,并预加载到组合键字典中。 - 立即修正类型不匹配问题,确保存入字典的值为
Task<string>类型。
内容的提问来源于stack exchange,提问作者Joel Lara
相关产品推荐
相关产品推荐

