MediatR 的 IMediator 默认注册为瞬时(Transient),每次解析都创建新实例;其线程安全性取决于 handler 的实现与生命周期配置,错误地将 Scoped 服务注入 Singleton handler 会导致运行时异常。

MediatR 默认注册为瞬时(Transient)
在 ASP.NET Core 中,MediatR 的 IMediator 接口默认通过 AddMediatR() 扩展方法注册为 ServiceLifetime.Transient。这意味着每次从 DI 容器解析 IMediator 时,都会创建一个新实例。
它本身不持有跨请求的共享状态,但它的行为依赖于你注册的 handlers 和 pipeline behaviors:
-
INotificationHandler、IRequestHandler等 handler 类型,默认也按Transient注册(除非你显式改用Scoped或Singleton) - 如果你把某个 handler 注册为
Singleton,而它内部又持有非线程安全的状态(比如普通字段、静态集合),那就可能出问题 -
MediatR内部使用IServiceProvider解析 handler,所以 handler 的生命周期必须 ≥IMediator实例的生命周期(否则会抛InvalidOperationException)
MediatR 是线程安全的,但 handler 不一定
MediatR 核心类型(如 Mediator 类)本身是无状态的,所有操作都委托给 DI 解析出的 handler,因此 IMediator 实例可被多线程并发调用 —— 这是安全的。
真正决定线程安全的是你写的 handler:
- 如果 handler 是纯函数式(只读参数、不改内部字段、不操作静态变量),那它是线程安全的
- 如果 handler 里用了
static List并直接 Add,就会发生竞态Cache = new() - 如果 handler 依赖
DbContext(Scoped),而你把它错误注册为 Singleton,运行时可能抛InvalidOperationException: A second operation started on this context before a previous operation completed
常见错误:在 Singleton handler 中注入 Scoped 服务
这是最典型的生命周期冲突,会导致运行时异常或数据错乱。例如:
public class MyHandler : IRequestHandler{ private readonly ApplicationDbContext _db; public MyHandler(ApplicationDbContext db) // ← DbContext 是 Scoped { _db = db; } public Task Handle(MyQuery request, CancellationToken ct) { return _db.Users.CountAsync(ct); } }
如果你这样注册:
services.AddSingleton, MyHandler>(); // ❌ 错误
就会导致同一个 MyHandler 实例被多个请求复用,而它持有的 _db 是 Scoped,早已被释放或正在被其他线程使用。
正确做法是让 MediatR 自动发现并注册为 Transient:
services.AddMediatR(cfg => cfg.RegisterServicesFromAssembly(typeof(MyHandler).Assembly)); // ✅ 默认就是 Transient
需要单例行为?别改 MediatR,改你的设计
有时候你会想“让某个 handler 全局只初始化一次”,比如加载配置、连接第三方 SDK。这时不要把 handler 设为 Singleton,而是:
- 把需要共享的状态提取到单独的
Singletonservice 中(如ISmsClientPool) - 在 handler 中注入该 Singleton 服务
- handler 本身仍保持 Transient,避免生命周期污染
这样既满足复用需求,又不破坏 DI 安全边界。MediatR 的设计哲学就是“消息即操作”,不是“消息即状态容器”。
真正容易被忽略的是:handler 的构造函数执行时机和作用域,远比 IMediator 本身的生命周期更关键。别盯着 MediatR 是单例还是瞬时,先盯紧你注册的每一个 handler 类型及其依赖树。










