瞬态IDisposable服务内存泄漏:寻求无需修改消费者的解决方案
解决瞬态IDisposable服务内存泄漏(无需修改消费者代码)
问题根源:从根服务提供者获取瞬态IDisposable服务时,.NET DI容器会自动跟踪实例,哪怕你用using手动Dispose,根容器依然持有引用,直到自身被释放,这就导致了内存泄漏。
下面是两种不用改现有消费者代码的解决方案:
方案一:.NET 8+ 禁用容器跟踪(最优)
直接修改注入代码,注册服务时关掉容器对该实例的跟踪:
public static IServiceCollection AddUnitOfWork(this IServiceCollection services) { var descriptor = ServiceDescriptor.Transient<IUnitOfWork>(provider => new UnitOfWork(provider.GetRequiredService<IConfiguration>())); // 告诉容器别跟踪这个实例的生命周期 descriptor.SetIsDisposableTrackingEnabled(false); services.Add(descriptor); return services; }
这样一来,实例的释放完全由你的using块负责,根容器不会再攥着引用不放,直接解决泄漏问题。
方案二:包装类自动管理服务范围(兼容.NET 6及更早版本)
如果还没升级到.NET 8,可以用包装类自动创建服务范围,让实例由范围容器跟踪,而非根容器:
第一步:写个包装类
public class ScopedUnitOfWorkWrapper : IUnitOfWork { private readonly IUnitOfWork _innerUow; private readonly IServiceScope _scope; public ScopedUnitOfWorkWrapper(IServiceProvider rootProvider) { // 每次获取服务时创建新的范围 _scope = rootProvider.CreateScope(); // 从范围里拿真实的UnitOfWork实例 _innerUow = _scope.ServiceProvider.GetRequiredService<UnitOfWork>(); } public void Dispose() { // 先释放内部实例,再销毁范围(范围会自动清理所有跟踪的对象) _innerUow.Dispose(); _scope.Dispose(); } public ValueTask DisposeAsync() { try { return _innerUow.DisposeAsync(); } finally { _scope.Dispose(); } } // 把IUnitOfWork里的其他方法都委托给_innerUow // 比如你的IUnitOfWork有SaveChangesAsync,就加: // public Task<int> SaveChangesAsync(CancellationToken cancellationToken = default) // { // return _innerUow.SaveChangesAsync(cancellationToken); // } }
第二步:修改注入代码
public static IServiceCollection AddUnitOfWork(this IServiceCollection services) { // 先注册真实的UnitOfWork实现(不让消费者直接拿) services.AddTransient<UnitOfWork>(); // 注册包装类作为IUnitOfWork的对外实现 services.AddTransient<IUnitOfWork>(provider => new ScopedUnitOfWorkWrapper(provider)); return services; }
原理:消费者拿到的是包装类,它会自动创建独立的服务范围,真实实例由范围容器跟踪。当using块结束调用Dispose时,包装类会释放内部实例和范围,范围容器会立刻清理所有跟踪的对象,根容器不会留任何引用,也就不会泄漏了。
内容的提问来源于stack exchange,提问作者Niccolò Pecora
相关产品推荐
相关产品推荐

