ConcurrentDictionary 详解:.NET 中的线程安全字典
什么是 ConcurrentDictionary?
ConcurrentDictionary<TKey, TValue>
是 .NET Framework 4.0+ 和 .NET Core/.NET 5+ 中引入的线程安全字典实现,位于 System.Collections.Concurrent
命名空间。它解决了多线程环境下操作字典时的同步问题,允许**多个线程同时读写字典**而无需显式加锁。
核心特性
- 线程安全:所有操作都是原子性的
- 高性能:使用细粒度锁(而非全局锁)
- 无锁读取:读操作通常不需要锁
- 原子方法:提供专门设计的线程安全方法
与普通 Dictionary 的对比
特性 | Dictionary | ConcurrentDictionary |
---|---|---|
线程安全 | ❌ 需要手动加锁 | ✅ 内置线程安全 |
读性能 | 高 | 非常高(无锁读取) |
写性能 | 高 | 较高(细粒度锁) |
并发支持 | 需要同步机制 | 原生支持并发 |
内存开销 | 较低 | 稍高(维护内部结构) |
基本使用
// 创建实例
var concurrentDict = new ConcurrentDictionary<string, int>();// 添加元素(线程安全)
concurrentDict.TryAdd("task1", 0);
concurrentDict.TryAdd("task2", 0);// 更新元素(原子操作)
concurrentDict.AddOrUpdate("task1", key => 1, // 添加时的工厂函数(key, oldValue) => oldValue + 1 // 更新时的函数
);// 安全获取值
if (concurrentDict.TryGetValue("task1", out int value))
{Console.WriteLine($"Task1 进度: {value}%");
}// 删除元素
concurrentDict.TryRemove("task2", out _);
关键方法详解
1. TryAdd()
bool success = concurrentDict.TryAdd("task3", 25);
// 成功添加返回 true,键已存在返回 false
2. TryUpdate()
int currentValue;
do {currentValue = concurrentDict["task1"];
} while (!concurrentDict.TryUpdate("task1", currentValue + 10, currentValue));
// 原子性更新:只有当前值等于 expectedValue 时才更新
3. AddOrUpdate()
// 添加或更新(原子操作)
int newValue = concurrentDict.AddOrUpdate("task1", key => 50, // 添加时的工厂函数(key, oldValue) => oldValue + 10 // 更新时的函数
);
4. GetOrAdd()
// 获取或添加(原子操作)
int value = concurrentDict.GetOrAdd("task4", key => 0);
// 如果 task4 不存在,初始化为 0
5. TryRemove()
if (concurrentDict.TryRemove("task2", out int removedValue))
{Console.WriteLine($"已删除 task2,原值: {removedValue}");
}
在任务进度系统中的应用
进度存储服务实现
public class ConcurrentTaskProgressService
{private readonly ConcurrentDictionary<string, TaskProgress> _progressStore = new ConcurrentDictionary<string, TaskProgress>();public void UpdateProgress(string taskId, int completed, int total){_progressStore.AddOrUpdate(taskId,// 添加新任务id => new TaskProgress{TaskId = id,Completed = completed,Total = total,LastUpdated = DateTime.UtcNow},// 更新现有任务(id, existing) =>{existing.Completed = completed;existing.Total = total;existing.LastUpdated = DateTime.UtcNow;return existing;});}public TaskProgress GetProgress(string taskId){return _progressStore.TryGetValue(taskId, out var progress) ? progress : null;}
}public class TaskProgress
{public string TaskId { get; set; }public int Completed { get; set; }public int Total { get; set; }public DateTime LastUpdated { get; set; }public int Percentage => Total > 0 ? (Completed * 100) / Total : 0;
}
最佳实践
-
优先使用原子方法
// ✅ 推荐 dict.AddOrUpdate(key, 1, (k, v) => v + 1);// ❌ 避免(非原子操作) if (dict.ContainsKey(key)) {dict[key] = dict[key] + 1; }
-
处理工厂函数副作用
// 工厂函数应简单无副作用 var value = dict.GetOrAdd(key, k => {// 避免耗时操作return CalculateInitialValue(k); });
-
迭代时处理并发修改
foreach (var pair in concurrentDict) {// 注意:迭代期间字典可能被修改Process(pair.Value); }
-
值类型注意事项
// 值类型更新需特殊处理 var dict = new ConcurrentDictionary<string, (int, DateTime)>();dict.AddOrUpdate("task", k => (0, DateTime.UtcNow),(k, v) => (v.Item1 + 1, DateTime.UtcNow) );
性能考量
- 读取密集型场景:性能接近无锁读取
- 写入密集型场景:比锁+Dictionary 性能高 2-3 倍
- 混合工作负载:在高并发下表现最佳
使用场景
- 缓存系统
- 实时监控/统计
- 并行计算中间结果
- 高并发计数器
- 任务调度系统(如所述进度监控)
总结
ConcurrentDictionary
是 .NET 中处理并发字典操作的首选方案,它:
- 提供线程安全的原子操作
- 比手动锁实现更高效
- 简化多线程编程模型
- 特别适合高频读写的场景
在任务进度监控系统中,使用 ConcurrentDictionary
可以安全高效地管理多个任务的进度状态,确保在高并发环境下数据的一致性和实时性。