.NET Core应用事件处理中使用依赖注入实例的解决方案咨询
在事件处理方法中使用DI实例的解决方案
这个问题本质是DI生命周期不匹配加上事件订阅导致的引用持有引发的,我给你拆解下问题根源和几种可行的解决思路:
问题根源分析
- 你的
GetSQLDependancyHandler作为MediatR的请求处理器,默认是Scoped生命周期——也就是说请求处理完成后,DI容器会回收这个实例以及它注入的Scoped服务(比如IMediator)。 - 而
ISqlDependancy如果是Singleton或者更长生命周期的话,它会持有Handler实例的引用(因为你订阅了它的OnTableUpdate事件),当事件触发时,Handler里的_mediator可能已经被释放,或者原请求的_cancellationToken已经被取消,导致调用失败。
可行解决方案
方案1:使用IServiceScopeFactory创建临时Scope(推荐)
这种方式适合ISqlDependancy是Singleton的场景,通过在事件触发时动态创建Scope来获取新鲜的Scoped服务:
public class GetSQLDependancyHandler : IRequestHandler<SQLDependancyQuery> { private readonly ISqlDependancy _sqlDependancy; private readonly IMapper _autoMapper; private readonly IServiceScopeFactory _scopeFactory; // 保存事件委托,方便后续取消订阅 private EventHandler<BroadCast> _eventHandler; public GetSQLDependancyHandler(IMapper autoMapper, ISqlDependancy sqlDependancy, IServiceScopeFactory scopeFactory) { _autoMapper = autoMapper; _sqlDependancy = sqlDependancy; _scopeFactory = scopeFactory; } public async Task<Unit> Handle(SQLDependancyQuery request, CancellationToken cancellationToken) { await _sqlDependancy.IngnightSQLDependancy("ObservationAdmission", "ServiceBroker"); // 初始化事件委托,避免捕获当前实例导致的内存泄漏 _eventHandler = _sqlDependancy_OnTableUpdate1; _sqlDependancy.OnTableUpdate += _eventHandler; // 请求结束时,自动取消事件订阅,防止内存泄漏 cancellationToken.Register(() => _sqlDependancy.OnTableUpdate -= _eventHandler); return Unit.Value; } private void _sqlDependancy_OnTableUpdate1(object sender, BroadCast e) { // 创建临时Scope,获取需要的Scoped服务 using var scope = _scopeFactory.CreateScope(); var mediator = scope.ServiceProvider.GetRequiredService<IMediator>(); // 不要使用原请求的CancellationToken,改用新的或默认的 var eventCancellationToken = CancellationToken.None; _ = mediator.Publish(new SQLDependancyDTO { Id = 1, Date = DateTime.Now, Message = "testmessage" }, eventCancellationToken); } }
关键注意事项:
- 一定要在请求结束时取消事件订阅,否则Singleton的
ISqlDependancy会一直持有Handler实例的引用,造成严重的内存泄漏。 - 事件处理中不要复用原请求的
CancellationToken,因为请求完成后这个Token已经被取消,会直接导致Publish操作失败。
方案2:通过事件参数传递依赖(适合可控事件触发逻辑)
如果ISqlDependancy的事件触发逻辑是你自己实现的,可以修改事件参数,直接把需要的依赖或数据传递进去:
// 自定义事件参数,包含所需服务 public class TableUpdateEventArgs : EventArgs { // 可以直接传递依赖,或者传递需要的数据 public IMediator Mediator { get; init; } public CancellationToken CancellationToken { get; init; } // 保留原有BroadCast的字段 public string Message { get; init; } } // 修改ISqlDependancy的事件定义 public interface ISqlDependancy { Task IngnightSQLDependancy(string tableName, string brokerName); event EventHandler<TableUpdateEventArgs> OnTableUpdate; } // 然后在Handler中订阅时捕获当前依赖 public async Task<Unit> Handle(SQLDependancyQuery request, CancellationToken cancellationToken) { await _sqlDependancy.IngnightSQLDependancy("ObservationAdmission", "ServiceBroker"); // 使用Lambda捕获当前的_mediator和cancellationToken var handler = (object? s, TableUpdateEventArgs args) => { _ = _mediator.Publish(new SQLDependancyDTO { Id = 1, Date = DateTime.Now, Message = args.Message }, cancellationToken); }; _sqlDependancy.OnTableUpdate += handler; // 同样要记得取消订阅 cancellationToken.Register(() => _sqlDependancy.OnTableUpdate -= handler); return Unit.Value; }
这种方式的好处是不需要额外创建Scope,但前提是你能修改ISqlDependancy的事件定义和触发逻辑,把需要的依赖传递到事件参数里。
方案3:对齐依赖生命周期(仅限特定场景)
如果ISqlDependancy和GetSQLDependancyHandler一样是Scoped生命周期,那么_mediator在事件触发时理论上还是有效的,但要注意:
- SqlDependency基于Service Broker的事件可能在后台线程触发,要确保当前Scope还没有被回收。
- 这种场景只适合短期的SqlDependency订阅,比如请求期间的临时监听,不适合长期后台监听。
总结
最通用的解决方案是方案1,通过IServiceScopeFactory动态创建Scope获取服务,同时一定要处理好事件订阅的取消,避免内存泄漏。如果能控制事件触发逻辑,方案2会更简洁。
内容的提问来源于stack exchange,提问作者Simon
相关产品推荐
相关产品推荐

