MediatR Publish()与ToListAsync()结合时出现阻塞问题求助
MediatR Publish() 结合 ToListAsync() 出现阻塞问题排查
当MediatR的Publish()方法与ToListAsync()结合使用时会出现阻塞(任务始终无法完成),但使用同步的ToList()时却能正常工作。以下是最小复现示例(因依赖数据库无法直接运行):
namespace ShowcasePublishGetsStuck { using System; using System.Linq; using System.Threading; using System.Threading.Tasks; using Database; using Database.Enums; using Database.Models; using MediatorEvents.Notifications; using MediatR; using Microsoft.EntityFrameworkCore; public class ShowcaseToListAsyncStuck { private MyDbContext _db; private readonly IMediator _mediator; public ShowcaseToListAsyncStuck( MyDbContext db, IMediator mediator) { _mediator = mediator; DemoGettingStuck(CancellationToken.None).Wait(); // 此处使用Wait()是核心问题诱因 } public async Task<bool> DemoGettingStuck(CancellationToken cancellationToken) { await _mediator.Publish(new KycCompanyCreatedEvent(0), cancellationToken); _mediator.Publish(new KycCompanyCreatedEvent(0), cancellationToken).Wait(cancellationToken); for (int i = 0; i < 1; i++) { await FunctionThatCallsSimplePublish(cancellationToken); // 正常执行 } var purchasesSync = _db.Purchases .Where(x => x.Id == 405) .ToList(); foreach (var _ in purchasesSync) { await FunctionThatCallsSimplePublish(cancellationToken); // 正常执行 } var purchasesAsync = await _db.Purchases .Where(x => x.Id == 405) .ToListAsync(cancellationToken); // 执行此方法后后续代码阻塞 foreach (var _ in purchasesAsync) { await FunctionThatCallsSimplePublish(cancellationToken); // 永远无法执行到此处 } return true; // 永远无法执行到此处 } public async Task FunctionThatCallsSimplePublish(CancellationToken cancellationToken = default) { await _mediator.Publish(new KycCompanyCreatedEvent(0), cancellationToken); await Task.Delay(0); } } }
环境信息
- .NET 6
- EntityFrameworkCore NuGet包版本:6.0.10
- MediatR版本:11.0.0(也尝试过12.1.1,问题复现)
处理程序代码
[UsedImplicitly] public class KycCompanyCreatedEventHandler : INotificationHandler<KycCompanyCreatedEvent> { private readonly IMyProvider _myProvider; private readonly MyDbContext _db; public KycCompanyCreatedEventHandler(IMyProvider myProvider, MyDbContext db) { _myProvider = myProvider; _db = db; } public Task Handle(KycCompanyCreatedEvent notification, CancellationToken cancellationToken) { return Task.CompletedTask; // 使用ToListAsync()时,此处断点永远不会触发 } }
问题根源
这不是MediatR或EF Core的Bug,而是混合同步阻塞与异步操作导致的线程死锁:
- 构造函数中调用
DemoGettingStuck(...)的.Wait()方法,会阻塞当前线程(通常是主线程或ASP.NET请求上下文线程)。 - 在同步上下文环境(比如ASP.NET)中,
await会捕获当前上下文,后续异步操作完成后需要回到该上下文继续执行。 - 当执行
ToListAsync()后,后续的_mediator.Publish()异步操作需要回到被.Wait()阻塞的上下文,此时上下文线程被占用,异步任务无法继续执行,从而形成死锁。 - 使用同步的
ToList()时,不会切换上下文,因此后续的异步操作能正常执行。
解决方案
- 禁止在构造函数中阻塞异步方法:构造函数是同步执行的,不要用
.Wait()/.Result()等方式阻塞异步逻辑。可以通过异步初始化接口(如ASP.NET的IAsyncInitializer)或者在合适的异步生命周期钩子中执行异步代码。 - 全程使用异步await:避免同步阻塞异步任务,保持异步代码的一致性,消除死锁的根源。
- 检查DI生命周期配置:确保MediatR处理程序、DbContext的生命周期配置合理,避免因生命周期冲突加剧上下文阻塞问题。
内容的提问来源于stack exchange,提问作者CodingYourLife
相关产品推荐
相关产品推荐

