c# ConditionalWeakTable 在并发场景下的应用
技术百科
幻夢星雲
发布时间:2026-01-23
浏览: 次 ConditionalWeakTable 是线程安全的,但仅限于自身字典操作;其方法内部加锁或使用原子操作,可防止数据结构损坏,适用于 RuntimeHelpers、WPF 附加属性等底层场景,但回调函数需幂等且避免非线程安全操作。
ConditionalWeakTable 是线程安全的,但不等于“自动解决所有并发问题”
ConditionalWeakTable 的所有公开方法(Add、GetValue、TryGetValue 等)内部都加了锁或使用了原子操作,因此**多线程调用不会导致数据结构损坏或崩溃**。这是它被设计用于 RuntimeHelpers.GetOrCreateObjectData、WPF 的附加属性、EF Core 的实体跟踪等底层场景的根本原因。
但要注意:它的线程安全仅限于自身字典操作。如果你在 createValueCallback 里做非线程安全操作(比如修改共享静态变量、访问未同步的集合),依然会出问题。
-
GetValue的回调函数可能被多个线程同时触发——即使键相同,只要尚未完成首次创建,多个线程都可能进入回调 - 回调执行期间,其他线程对同一键的
GetValue会阻塞等待;但不同键之间完全无干扰 - 没有内置的“只让第一个线程执行回调,其余等待结果”的去重逻辑——它靠内部锁实现串行化,不是靠 CAS 或 double-check
用 GetValue 创建单例式附加状态时,回调必须幂等
典型场景是给任意对象(比如 Stream 或自定义类实例)附着一个线程本地或生命周期绑定的辅助对象,又不想阻止原对象被回收。这时常配合 GetValue 使用:
var table = new ConditionalWeakTable>(); var resource = table.GetValue(obj, _ => new Lazy (() => new ExpensiveResource()));
上面写法看似简洁,但有隐患:Lazy 的构造本身不执行初始化,而 resource.Value 第一次访问才创建实例——这意味着多个线程仍可能并发进入

new ExpensiveResource(),除非你用 LazyThreadSafetyMode.ExecutionAndPublication(默认就是它)。
- 更稳妥的做法是直接在回调里返回已构建好的对象,或者确保构造逻辑本身可重入
- 不要在回调中调用
lock去保护外部资源——这容易引发死锁,因为GetValue内部已有锁 - 若需延迟初始化 + 线程安全,优先用
Lazy包一层,且显式指定LazyThreadSafetyMode.ExecutionAndPublication
别把它当 ConcurrentDictionary 用
ConditionalWeakTable 和 ConcurrentDictionary 完全不是一回事:
- 前者 key 是弱引用(key 对象被 GC 后,对应条目自动消失),后者是强引用
- 前者不支持枚举(
Keys、Values、GetEnumerator全部抛NotSupportedException),后者支持 - 前者没有
Count属性,无法知道当前挂了多少关联项;后者有 - 前者不能手动删除(没
Remove方法),只能等 key 被回收;后者可主动清理
如果你需要“带弱引用语义的并发字典”,ConditionalWeakTable 不满足需求——得自己封装,比如用 ConcurrentDictionary 并定期清理失效引用,但这会失去 ConditionalWeakTable 的自动清理优势。
调试时看不到内容,别以为没生效
在 Visual Studio 调试器里,ConditionalWeakTable 的字段(如 m_tables)是私有的、内部结构复杂,且调试器不会触发其弱引用遍历逻辑。所以断点停住后展开对象,大概率看到空的 Keys 或报错“not supported”。
验证是否生效,唯一可靠方式是:
- 写单元测试,用
GC.Collect()+GC.WaitForPendingFinalizers()后检查原 key 是否还能通过TryGetValue拿到值 - 在回调里打日志,确认是否被调用、调用次数是否符合预期(尤其高并发下)
- 用内存分析工具(如 dotMemory)查看是否存在意外的强引用链阻止 key 回收
它的行为藏在 GC 和运行时协作深处,表面安静,实际很忙。
# ai
# 这是
# 如果你
# 多个
# 第一个
# 还能
# 已有
# 首次
# 数据结构
# 工具
# 并发
# 对象
# double
# stream
# c#
# visual studio
# 线程
# 死锁
# 回调
# 多线程
# 封装
# wpf
# count
# 回调函数
# Resource
# 有锁
相关栏目:
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
AI推广<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
SEO优化<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
技术百科<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
谷歌推广<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
百度推广<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
网络营销<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
案例网站<?muma echo $count; ?>
】
<?muma
$count = M('archives')->where(['typeid'=>$field['id']])->count();
?>
【
精选文章<?muma echo $count; ?>
】
相关推荐
- Win11怎么更改计算机名_Windows11系统
- MAC怎么截图并快速编辑_MAC自带截图快捷键与标
- 如何在Golang中实现微服务服务拆分_Golan
- Python模块的__name__属性如何由导入方
- Mac如何修改Hosts文件?(本地开发与屏蔽网站
- Win11怎么开启移动热点_Windows11共享
- Windows 11怎么更改锁屏超时时间_Wind
- php与c语言在嵌入式中有何区别_对比两者在硬件控
- Win11怎么关闭资讯和兴趣_Windows11任
- php增删改查在php8里有什么变化_新特性对cu
- Windows10如何更改桌面背景_Win10个性
- Windows系统文件被保护机制阻止怎么办_权限不
- Win11怎么关闭粘滞键_彻底禁用Windows
- Flask 表单数据通过 SMTP 发送邮件的完整
- 如何在Golang中处理数据库事务错误_回滚和日志
- Go 语言标准库为何不提供泛型切片的 Contai
- PHP 中 require() 语句返回值的用法详
- Windows的便笺功能如何使用?(桌面备忘技巧)
- Win11怎样安装企业微信_Win11安装企业微信
- Win11怎么设置默认终端应用_Windows11
- Win11文件夹预览图不显示怎么办_Win11缩略
- Win11如何设置ipv6 Win11开启IPv6
- Mac系统更新下载慢或失败怎么办_解决macOS升
- PythonGIL机制理解_多线程限制解析【教程】
- Win11怎么设置虚拟桌面 Win11新建多桌面切
- Mac怎么给文件夹加密_Mac创建加密磁盘映像教程
- Win10系统怎么查看端口状态_Windows10
- 如何使用Golang处理网络超时错误_Golang
- c++如何使用std::bind绑定函数参数_c+
- 短链接怎么自定义还原php_修改解码规则适配需求【
- Windows资源管理器总是卡顿或重启怎么办?(修
- Win11怎么清理C盘系统日志_Win11清理系统
- 如何在Golang中实现WebSocket广播_使
- MAC怎么在照片中添加水印_MAC自带编辑工具文字
- Win10怎么卸载剪映_Win10彻底卸载剪映方法
- Windows10如何更改任务栏高度_Win10解
- Win11怎么关闭防火墙通知_屏蔽Win11安全中
- 如何在Golang中引入测试模块_Golang测试
- Win11相机打不开提示错误怎么修_相机权限开启与
- Windows怎样拦截QQ浏览器广告_Window
- Mac如何将HEIC图片格式转为JPG_Mac批量
- c++ atoi和atof函数用法_c++字符数组
- 如何使用Golang log记录不同级别日志_Go
- MAC如何安装Git版本控制工具_MAC开发环境配
- php下载安装后memory_limit怎么设置_
- Win11如何设置计划任务 Win11定时执行程序
- Win10如何关闭安全中心所有通知 Win10禁用
- Win11怎么自动隐藏任务栏_Win11全屏显示设
- Python变量绑定机制_引用模型解析【教程】
- 使用类变量定义字符串常量时的类型安全最佳实践

QQ客服