You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

.NET Core应用事件处理中使用依赖注入实例的解决方案咨询

在事件处理方法中使用DI实例的解决方案

这个问题本质是DI生命周期不匹配加上事件订阅导致的引用持有引发的,我给你拆解下问题根源和几种可行的解决思路:

问题根源分析

  1. 你的GetSQLDependancyHandler作为MediatR的请求处理器,默认是Scoped生命周期——也就是说请求处理完成后,DI容器会回收这个实例以及它注入的Scoped服务(比如IMediator)。
  2. 而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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.30 18:47:30