目录

WPF-DI 容器如何保证线程安全?

WPF 是桌面应用,没有每个请求一个 Scope 的概念,因此通常只会使用两类生命周期:Singleton(单例)和 **Transient(瞬时),**因此从 这两种生命周期 出发解释 DI 容器如何保证线程安全。

  1. 单例(Singleton)如何保证线程安全?

DI 容器必须确保:

  • 单例 只创建一次
  • 多线程访问时不会创建多个实例(避免线程竞争导致双创建)

通常容器内部会实现:

private object _lockObj = new object();
private object _instance;

创建逻辑类似:

if (_instance == null)
{
    lock(_lockObj)
    {
        if (_instance == null)
        {
            _instance = CreateInstance();
        }
    }
}

WPF 中也能出现多线程访问?

虽然 UI 线程只有一个,但后台线程(如 Task、后台服务)可能会请求同一个服务。

如果容器不加锁,会产生:

  • 同一个单例类被创建两次
  • 静态资源被重复初始化
  • 逻辑出现混乱

DI 容器内部通过加锁保证:

  • 多线程访问时单例只会创建一次
  • 所有线程得到的都是同一个实例
  1. 瞬时(Transient)如何保证线程安全?

Transient 每次调用都会创建一个新对象,所以并发时不会共享实例,因此通常 不需要锁

但容器内部依然会注意两点:

1. 解析依赖图本身是线程安全的

容器内部会对以下行为加锁:

  • 构建类型映射字典
  • 注册服务
  • 创建表达式树或工厂缓存

避免多个线程同时注册或构造表达式树时互相干扰。

2. 每次创建对象是独立的

因此不需要共享,也不需要锁。

  1. 注册阶段的线程安全

即使没有 Scope,DI 容器也必须保证:

注册是线程安全的

(所有容器都这样)

内部使用:

  • lock()
  • ConcurrentDictionary
  • 冻结注册表(ASP.NET Core 容器在第一次解析后禁止修改注册)

原因:

如果两个线程同时 Register / Resolve,会导致容器的服务表不一致。

WPF 中一般在应用启动时(App.xaml.cs)注册全部服务
所以冲突较少,但容器仍然会保护自己不被并发破坏。

  1. 线程安全与 UI 线程无关

即便 WPF UI 是单线程,但后台线程可能访问容器,例如:

  • Task 运行后台逻辑
  • 消息总线触发回调
  • 仓储层做异步操作
  • EF Core 操作
  • 网络请求回调

所有这些都可能通过容器 Resolve 服务。

因此容器必须确保:

  • 单例不会被重复创建
  • 服务注册表不会被竞争修改
  • 瞬时服务可以并发创建且不会互相影响
  1. WPF 场景下的生命周期总结

生命周期是否线程安全DI 如何保证Singleton必须线程安全双重检查锁 / 锁机制 / 并发字典Transient天然线程安全每次创建独立对象,无共享状态注册注册表必须线程安全内部加锁或 ConcurrentDictionary解析过程必须线程安全创建对象过程加锁,确保依赖图一致

  1. 你可以把 DI 的线程安全理解成“武林秘籍库”

假设你有一个 秘籍仓库(DI 容器)

单例 = “全门派唯一的绝学手抄本”

必须保证只复制一次,于是仓库管理员:

  • 上锁
  • 确保绝学只有一份

Transient = “随便印的练功纸张”

谁来就给一份,不共享,也不会冲突。

注册阶段 = “建秘籍库、放秘籍、贴标签”

这个阶段发生在门派创建之初(程序启动)
必须保护仓库结构不被破坏,也要上锁。

总结(WPF 场景,无 Scope)

DI 容器通过:

  • 对单例创建加锁
  • 对注册/解注册加锁或使用并发字典
  • 对依赖图构建加锁
  • Transient 通过不共享实例自然线程安全

从而保证:

  • 服务解析不会因为多线程导致错误**
  • 单例不会被创建多次
  • 注册不会产生竞争冲突