我有以下最小示例来重现我的现实生活应用程序的问题:
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() 上实例化。在 ServiceA 的 Start() 期间,调用 ServiceB 上的 Connect() 并抛出异常。异常被转发到 Build(),该方法失败并且未创建容器。
我对阅读 Autofac 文档的期望:
自动处置 要利用自动确定性处置,您的组件必须实现 IDisposable。然后,您可以根据需要注册组件,并在解析组件的每个生命周期结束时,将调用组件上的 Dispose() 方法。
可能会调用创建的 ServiceB 上的 Dipose()。不是这种情况。 ServiceB 位于我的应用程序中的某个位置,我无法处置它,因为 IContainer 尚未创建。
ChatGPT 建议捕获异常并稍后在 IContainer 上调用 Dispose()。这意味着,我需要检查 ServiceB 上的 Connect() 是否成功(property bool IsConnected),如果没有,我调用 Dispose()。
当然,这可以解决我的问题。但同样,我的期望是 Autofac 通过 LifetimeScope 为我处理这个问题。
这是关于使用
IStartable
的一个有趣的边缘情况。我会推荐:
IStartable
- 当前问题的标题(“Autofac:在稍后的服务创建抛出异常时处理未调用的已创建服务”)非常具有误导性。它不仅仅是任何旧服务,它非常具体地围绕在容器构建时自动实例化的事物。这将有助于其他有类似问题的人稍后找到您的问题。顺便说一句,我发现您正在使用必需属性而不是构造函数参数进行设置。从技术上讲,您实际上不必填充所需的参数来构造对象 -
required
是语法编译器糖,而不是对象激活指南。
更具体地说:
// This won't work, it'll be caught by the compiler:
new ServiceA();
// But this totally works:
Activator.CreateInstance(typeof(ServiceA));
您应该意识到这种区别,因为并非所有 DI 框架都会为您设置所需的属性。例如,我认为 MS DI 框架目前不会这样做。如果某些东西是真正需要的,你应该将它作为构造函数参数,即使这有时会变得混乱。