MediatR 的 Publish
默认是同步顺序执行,不是并发
很多人看到
IPublisher.Publish返回
Task就默认它天然支持并发,其实不然。MediatR 的默认行为是:所有
IAsyncNotificationHandler<t></t>按注册顺序**逐个 await**,一个 handler 抛异常会中断后续 handler 执行(除非显式配置异常处理策略)。这和你手动写
await handler1.Handle(...); await handler2.Handle(...);效果一致。
ParallelNoWait
策略:真正并发但完全放弃等待与错误传播
这是 MediatR 8+ 引入的并发策略,通过
ServiceCollection配置启用:
services.AddMediatR(cfg =>
{
cfg.RegisterServicesFromAssembly(typeof(Program).Assembly);
cfg.NotificationPublisher = new ParallelNoWaitPublisher(); // 关键
});
它会把所有 handler 包装进
Task.Run并发启动,然后立即返回已完成的
Task.CompletedTask—— 也就是说:
Publish调用后几乎立刻返回,不等任何 handler 完成 handler 内部抛出的异常会被吞掉(仅记录到
ILogger),不会向上冒泡 无法感知 handler 是否成功、耗时多久、是否被取消 不适合依赖 handler 副作用(如发消息、更新缓存)后继续逻辑的场景
SyncContinueOnException
是假“并发”,本质是同步兜底
这个策略名字有误导性:它**不并发**,也不并行。它的作用只是让某个 handler 抛异常时,不影响其他 handler 继续执行 —— 仍按注册顺序同步调用,只是把每个
Handle包在
try/catch里:
services.AddMediatR(cfg =>
{
cfg.RegisterServicesFromAssembly(typeof(Program).Assembly);
cfg.NotificationPublisher = new SyncContinueOnExceptionPublisher();
});
典型适用场景:
多个日志/埋点 handler,一个失败不该阻塞另一个 监控类通知(如发送指标),允许部分失败 你明确不需要 await 结果,但又希望保留调用顺序和可调试性注意:它仍会等待全部 handler 同步执行完才返回
Task,只是异常不中断流程。
自己实现可控并发:用 Task.WhenAll
+ 显式错误处理
如果既要并发,又要捕获结果或控制失败策略,MediatR 不提供开箱即用方案,得自己封装。常见做法是在业务层手动聚合 handler 调用:
var tasks = handlers.Select(h => h.Handle(notification, cancellationToken));
var results = await Task.WhenAll(tasks); // 等全部完成,任一失败则整体失败
// 或带错误隔离:
var results = await Task.WhenAll(
handlers.Select(h =>
Task.Run(() => h.Handle(notification, cancellationToken))
.ContinueWith(t => t.Exception?.InnerException, TaskContinuationOptions.OnlyOnFaulted)
.ContinueWith(t => t.Result ?? null)
)
);
关键点:
必须从 DI 容器解析IEnumerable<iasyncnotificationhandler>></iasyncnotificationhandler>,不能靠 MediatR 自动分发
Task.WhenAll失败时抛第一个异常;若要忽略部分失败,需用
ContinueWith或
await task.OrNothing()类辅助方法 并发数不受控时可能压垮下游(如 DB 连接池),建议配合
SemaphoreSlim限流
MediatR 的 Publish 并发能力很轻量,真正需要可靠异步协作时,往往该考虑事件总线(如 MassTransit)或后台任务队列(如 Hangfire)了。
