什么是 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
可以安全高效地管理多个任务的进度状态,确保在高并发环境下数据的一致性和实时性。