MediatR 默认注册为瞬时(Transient)
在 ASP.NET Core 中,
MediatR的
IMediator接口默认通过
AddMediatR()扩展方法注册为
ServiceLifetime.Transient。这意味着每次从 DI 容器解析
IMediator时,都会创建一个新实例。
它本身不持有跨请求的共享状态,但它的行为依赖于你注册的 handlers 和 pipeline behaviors:
INotificationHandler<t></t>、
IRequestHandler<trequest tresponse></trequest>等 handler 类型,默认也按
Transient注册(除非你显式改用
Scoped或
Singleton) 如果你把某个 handler 注册为
Singleton,而它内部又持有非线程安全的状态(比如普通字段、静态集合),那就可能出问题
MediatR内部使用
IServiceProvider解析 handler,所以 handler 的生命周期必须 ≥
IMediator实例的生命周期(否则会抛
InvalidOperationException)
MediatR 是线程安全的,但 handler 不一定
MediatR核心类型(如
Mediator类)本身是无状态的,所有操作都委托给 DI 解析出的 handler,因此
IMediator实例可被多线程并发调用 —— 这是安全的。
真正决定线程安全的是你写的 handler:
如果 handler 是纯函数式(只读参数、不改内部字段、不操作静态变量),那它是线程安全的 如果 handler 里用了static List<int> Cache = new()</int>并直接 Add,就会发生竞态 如果 handler 依赖
DbContext(Scoped),而你把它错误注册为 Singleton,运行时可能抛
InvalidOperationException: A second operation started on this context before a previous operation completed
常见错误:在 Singleton handler 中注入 Scoped 服务
这是最典型的生命周期冲突,会导致运行时异常或数据错乱。例如:
public class MyHandler : IRequestHandler<MyQuery, string>
{
private readonly ApplicationDbContext _db;
public MyHandler(ApplicationDbContext db) // ← DbContext 是 Scoped
{
_db = db;
}
public Task<string> Handle(MyQuery request, CancellationToken ct)
{
return _db.Users.CountAsync(ct);
}
}
如果你这样注册:
services.AddSingleton<IRequestHandler<MyQuery, string>, 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 类型及其依赖树。
