ConditionalWeakTable 是 .NET 提供的线程安全、弱引用键值映射结构,专为对象附加生命周期绑定数据设计;它通过锁+分段哈希表实现原子操作,避免内存泄漏,适用于为第三方类型动态挂载上下文,但不可替代 ConcurrentDictionary 或用于枚举。
ConditionalWeakTable 是 .NET 提供的线程安全、弱引用的键值映射结构,核心用途是为对象“附加”生命周期绑定的数据,且不阻止被附加对象的回收。它内部用锁+分段哈希表实现,所有公开方法(Add、GetOrCreateValue、TryGetValue)都是线程安全的——这点和 Dictionary 有本质区别:后者即使加了锁,也需手动管理弱引用逻辑,还容易因强引用导致内存泄漏。
典型使用场景包括:为第三方类型(如 Stream 或 ASP.NET Core 的 HttpContext)动态挂载上下文信息,或在 AOP 中注入追踪 ID,而无需修改原类型定义。
必须用 GetOrCreateValue,而不是先 TryGetValue 再 Add —— 后者存在竞态:两个线程同时发现 key 不存在,都会尝试 Add,触发重复初始化甚至异常(ArgumentException: An item with the same key has already been added)。
GetOrCreateValue 内部保证“查找 + 创建”原子性,即使传入的工厂委托被多次调用,也仅有一个结果被最终采纳(其余调用会被丢弃)ConcurrentDictionary),可接受;但避免在里面做 I/O 或锁操作GetOrCreateValue 返回的值再反复使用——它只是对附加数据的强引用快照,不影响底层弱引用行为var table = new ConditionalWeakTable
它不是通用并发字典,不能替代 ConcurrentDictionary:
Keys、Values 或 GetEnumerator)——因为键是弱引用,遍历时可能已回收,无法可靠列出全部项Remove 方法(.NET 6+ 才有 Remove,且仅用于调试/测试,生产中极少需要主动删)table[k
ey] 索引器不存在;只能通过 GetOrCreateValue 或 TryGetValue 访问new ConditionalWeakTable() ,运行时不会报错,但字符串常量池中的字面量会被永久驻留,失去弱引用意义ThreadLocal 绑定的是“线程”,ConditionalWeakTable 绑定的是“对象实例”。两者解决的问题维度不同:
ConditionalWeakTable
ThreadLocal
ThreadLocal 实例)是可行的,但要注意 ThreadLocal 本身也要被弱引用持有,否则会阻碍线程终结时的清理AsyncLocal 更适合异步上下文传播,而 ConditionalWeakTable 仍是最轻量的对象级附着方案真正容易被忽略的是:ConditionalWeakTable 的线程安全性只覆盖其自身 API,不延伸到你附加的值内部。如果你存了一个非线程安全的 List,多个线程同时操作它依然会出问题——附加数据本身的线程安全要自行保障。