ConcurrentDictionary 详解:.NET 中的线程安全字典

发布于:2025-08-09 ⋅ 阅读:(18) ⋅ 点赞:(0)

什么是 ConcurrentDictionary?

ConcurrentDictionary<TKey, TValue> 是 .NET Framework 4.0+ 和 .NET Core/.NET 5+ 中引入的线程安全字典实现,位于 System.Collections.Concurrent 命名空间。它解决了多线程环境下操作字典时的同步问题,允许**多个线程同时读写字典**而无需显式加锁。

核心特性

  1. 线程安全:所有操作都是原子性的
  2. 高性能:使用细粒度锁(而非全局锁)
  3. 无锁读取:读操作通常不需要锁
  4. 原子方法:提供专门设计的线程安全方法

与普通 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;
}

最佳实践

  1. 优先使用原子方法

    // ✅ 推荐
    dict.AddOrUpdate(key, 1, (k, v) => v + 1);
    
    // ❌ 避免(非原子操作)
    if (dict.ContainsKey(key)) {
        dict[key] = dict[key] + 1;
    }
    
  2. 处理工厂函数副作用

    // 工厂函数应简单无副作用
    var value = dict.GetOrAdd(key, k => {
        // 避免耗时操作
        return CalculateInitialValue(k);
    });
    
  3. 迭代时处理并发修改

    foreach (var pair in concurrentDict)
    {
        // 注意:迭代期间字典可能被修改
        Process(pair.Value);
    }
    
  4. 值类型注意事项

    // 值类型更新需特殊处理
    var dict = new ConcurrentDictionary<string, (int, DateTime)>();
    
    dict.AddOrUpdate("task", 
        k => (0, DateTime.UtcNow),
        (k, v) => (v.Item1 + 1, DateTime.UtcNow)
    );
    

性能考量

  1. 读取密集型场景:性能接近无锁读取
  2. 写入密集型场景:比锁+Dictionary 性能高 2-3 倍
  3. 混合工作负载:在高并发下表现最佳

使用场景

  1. 缓存系统
  2. 实时监控/统计
  3. 并行计算中间结果
  4. 高并发计数器
  5. 任务调度系统(如所述进度监控)

总结

ConcurrentDictionary 是 .NET 中处理并发字典操作的首选方案,它:

  • 提供线程安全的原子操作
  • 比手动锁实现更高效
  • 简化多线程编程模型
  • 特别适合高频读写的场景

在任务进度监控系统中,使用 ConcurrentDictionary 可以安全高效地管理多个任务的进度状态,确保在高并发环境下数据的一致性和实时性。


网站公告

今日签到

点亮在社区的每一天
去签到