Autofac:后续服务创建抛异常时,已创建服务的Dispose方法未被调用
问题复现代码
服务类实现
public class ServiceA : IStartable, IDisposable { public required ServiceB ServiceB { get; set; } public void Start() { ServiceB.Connect(); } public void Dispose() { Console.WriteLine("ServiceA disposed"); } } public class ServiceB : IDisposable { public void Connect() { throw new TimeoutException(); } public void Dispose() { Console.WriteLine("ServiceB disposed"); } }
控制台应用逻辑
var builder = new ContainerBuilder(); builder.RegisterType(typeof(ServiceA)).AsSelf().As<IStartable>().SingleInstance(); builder.RegisterType(typeof(ServiceB)).AsSelf().InstancePerDependency(); var container = builder.Build(); Console.ReadKey(); container.Dispose(); Console.ReadKey();
问题场景与预期不符点
在依赖链中,ServiceB是ServiceA的依赖,调用Build()时会先实例化ServiceB。由于ServiceA实现了IStartable,其Start()方法会在容器构建阶段自动执行,其中调用ServiceB.Connect()抛出TimeoutException,直接导致容器创建失败。
根据Autofac官方文档的说明:
自动释放
要利用自动确定性释放功能,组件必须实现IDisposable接口。你可以按需注册组件,在解析组件的每个生命周期范围结束时,组件的Dispose()方法会被调用。
原本期望已创建的ServiceB实例会被自动调用Dispose(),但实际并未触发。同时因为IContainer未创建成功,无法通过容器手动释放组件。
原因分析
Autofac的自动释放机制依赖**生命周期范围(Lifetime Scope)**的正常初始化与销毁流程。当容器构建失败时,根生命周期范围并未完全初始化完成,已创建的组件会成为未被跟踪的“孤儿”实例,不在Autofac的自动释放管理范围内,因此不会触发Dispose()。
可行解决方案
方案1:捕获构建异常,手动跟踪并释放实例
通过注册回调跟踪已创建的ServiceB实例,在构建失败时手动释放:
List<IDisposable> trackedInstances = new List<IDisposable>(); IContainer? container = null; try { var builder = new ContainerBuilder(); builder.RegisterType<ServiceA>().AsSelf().As<IStartable>().SingleInstance(); builder.RegisterType<ServiceB>() .AsSelf() .InstancePerDependency() .OnActivated(e => trackedInstances.Add(e.Instance)); container = builder.Build(); Console.ReadKey(); } catch (Exception) { foreach (var instance in trackedInstances) { instance.Dispose(); } } finally { container?.Dispose(); Console.ReadKey(); }
方案2:将初始化逻辑移出容器构建阶段
避免在容器构建时执行可能失败的初始化操作,改为容器创建完成后手动触发:
var builder = new ContainerBuilder(); builder.RegisterType<ServiceA>().AsSelf().SingleInstance(); builder.RegisterType<ServiceB>().AsSelf().InstancePerDependency(); var container = builder.Build(); try { var serviceA = container.Resolve<ServiceA>(); serviceA.Start(); // 手动执行初始化,异常不会导致容器构建失败 Console.ReadKey(); } catch (TimeoutException) { // 初始化失败时,容器已正常创建,可通过Dispose释放所有组件 } finally { container.Dispose(); Console.ReadKey(); }
方案3:扩展Autofac构建逻辑处理异常
通过自定义容器构建逻辑,在异常发生时清理已创建的组件实例,这种方式适合需要全局统一处理的场景。
总结
Autofac的自动释放仅针对正常初始化完成的生命周期范围生效,容器构建失败属于异常场景,无法自动处理未被跟踪的组件释放。需要通过手动跟踪实例、调整初始化时机等方式,确保实现IDisposable的组件被正确释放。
内容的提问来源于stack exchange,提问作者Sputnik24

