WPF-DI 容器如何保证线程安全?
目录
WPF 是桌面应用,没有每个请求一个 Scope 的概念,因此通常只会使用两类生命周期:Singleton(单例)和 **Transient(瞬时),**因此从 这两种生命周期 出发解释 DI 容器如何保证线程安全。
- 单例(Singleton)如何保证线程安全?
DI 容器必须确保:
- 单例 只创建一次
- 多线程访问时不会创建多个实例(避免线程竞争导致双创建)
通常容器内部会实现:
private object _lockObj = new object();
private object _instance;创建逻辑类似:
if (_instance == null)
{
lock(_lockObj)
{
if (_instance == null)
{
_instance = CreateInstance();
}
}
}WPF 中也能出现多线程访问?
虽然 UI 线程只有一个,但后台线程(如 Task、后台服务)可能会请求同一个服务。
如果容器不加锁,会产生:
- 同一个单例类被创建两次
- 静态资源被重复初始化
- 逻辑出现混乱
DI 容器内部通过加锁保证:
- 多线程访问时单例只会创建一次
- 所有线程得到的都是同一个实例
- 瞬时(Transient)如何保证线程安全?
Transient 每次调用都会创建一个新对象,所以并发时不会共享实例,因此通常 不需要锁。
但容器内部依然会注意两点:
1. 解析依赖图本身是线程安全的
容器内部会对以下行为加锁:
- 构建类型映射字典
- 注册服务
- 创建表达式树或工厂缓存
避免多个线程同时注册或构造表达式树时互相干扰。
2. 每次创建对象是独立的
因此不需要共享,也不需要锁。
- 注册阶段的线程安全
即使没有 Scope,DI 容器也必须保证:
注册是线程安全的
(所有容器都这样)
内部使用:
- lock()
- ConcurrentDictionary
- 冻结注册表(ASP.NET Core 容器在第一次解析后禁止修改注册)
原因:
如果两个线程同时 Register / Resolve,会导致容器的服务表不一致。
WPF 中一般在应用启动时(App.xaml.cs)注册全部服务
所以冲突较少,但容器仍然会保护自己不被并发破坏。
- 线程安全与 UI 线程无关
即便 WPF UI 是单线程,但后台线程可能访问容器,例如:
- Task 运行后台逻辑
- 消息总线触发回调
- 仓储层做异步操作
- EF Core 操作
- 网络请求回调
所有这些都可能通过容器 Resolve 服务。
因此容器必须确保:
- 单例不会被重复创建
- 服务注册表不会被竞争修改
- 瞬时服务可以并发创建且不会互相影响
- WPF 场景下的生命周期总结
生命周期是否线程安全DI 如何保证Singleton必须线程安全双重检查锁 / 锁机制 / 并发字典Transient天然线程安全每次创建独立对象,无共享状态注册注册表必须线程安全内部加锁或 ConcurrentDictionary解析过程必须线程安全创建对象过程加锁,确保依赖图一致
- 你可以把 DI 的线程安全理解成“武林秘籍库”
假设你有一个 秘籍仓库(DI 容器):
单例 = “全门派唯一的绝学手抄本”
必须保证只复制一次,于是仓库管理员:
- 上锁
- 确保绝学只有一份
Transient = “随便印的练功纸张”
谁来就给一份,不共享,也不会冲突。
注册阶段 = “建秘籍库、放秘籍、贴标签”
这个阶段发生在门派创建之初(程序启动)
必须保护仓库结构不被破坏,也要上锁。
总结(WPF 场景,无 Scope)
DI 容器通过:
- 对单例创建加锁
- 对注册/解注册加锁或使用并发字典
- 对依赖图构建加锁
- Transient 通过不共享实例自然线程安全
从而保证:
- 服务解析不会因为多线程导致错误**
- 单例不会被创建多次
- 注册不会产生竞争冲突