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

BackgroundService内部事件未解绑导致内存泄漏,如何正确退订事件?

内存泄漏原因

单例的IEventsHandler实例会持有事件订阅委托的引用,而该委托关联了MyWorker实例,只要单例IEventsHandler不销毁,MyWorker实例就会一直被引用无法被GC回收,最终产生内存泄漏。

修复方案

重写BackgroundService的DisposeAsync方法,在服务生命周期结束时主动解绑事件即可:

public class MyWorker : BackgroundService
{
    private readonly IEventsHandler _eventsHandler;
    private readonly IServiceProvider _serviceProvider;
    private readonly CancellationTokenSource _stoppingCts = new();

    public MyWorker(IEventsHandler eventsHandler,
                    IServiceProvider serviceProvider)
    {
        _eventsHandler = eventsHandler;
        _serviceProvider = serviceProvider;
        _eventsHandler.AlertOccurred += AlertEventOccurred;
    }

    protected override Task ExecuteAsync(CancellationToken stoppingToken)
    {
        // 事件驱动场景下只需要监听停止信号即可
        return WaitForStopAsync(stoppingToken);
    }

    private async void AlertEventOccurred(object sender, AlertEvent e)
    {
        try
        {
            using var scope = _serviceProvider.CreateScope();
            var service = scope.ServiceProvider.GetRequiredService<AlertService>();
            await service.SaveAlert(e, _stoppingCts.Token);
        }
        catch (Exception ex)
        {
            // 自行处理异常,避免async void异常逃逸导致进程崩溃
            Console.WriteLine($"处理告警事件失败:{ex.Message}");
        }
    }

    private async Task WaitForStopAsync(CancellationToken stoppingToken)
    {
        using var linkedCts = CancellationTokenSource.CreateLinkedTokenSource(stoppingToken, _stoppingCts.Token);
        await Task.Delay(Timeout.Infinite, linkedCts.Token);
    }

    public override async ValueTask DisposeAsync()
    {
        // 先解绑事件
        _eventsHandler.AlertOccurred -= AlertEventOccurred;
        // 取消未完成的操作
        _stoppingCts.Cancel();
        await base.DisposeAsync();
        _stoppingCts.Dispose();
    }
}

代码存在的不良实践

  • async void事件处理方法:async void方法抛出的未处理异常会直接触发进程级别的崩溃,且无法被外层逻辑捕获,必须在方法内部全量捕获所有异常。如果要更好地适配异步场景,建议将事件委托定义为Func<object, AlertEvent, CancellationToken, Task>类型,替代传统的同步EventHandler<T>。
  • 不必要的Task.Run封装:EventsHandler.AddAlertEventAsync中用Task.Run触发事件属于无意义的操作,触发事件本身是轻量同步逻辑,额外的线程调度只会增加开销,还会提升异常排查的复杂度。可以直接修改为:
    public async Task AddAlertEventAsync(AlertEvent ev)
    {
        AlertOccurred?.Invoke(this, ev);
        // 如果改为异步事件委托,改为await AlertOccurred?.Invoke(this, ev, default)即可
        await Task.CompletedTask;
    }
    
  • 缺少服务停止的联动逻辑:原代码没有利用BackgroundService的生命周期管理能力,既没有监听停止信号终止未完成的业务操作,也没有在服务销毁时清理资源,是本次内存泄漏的直接诱因。

内容的提问来源于stack exchange,提问作者solDev

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.24 10:06:03