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
相关产品推荐
相关产品推荐

