Autofac 清除
在单个工作单元中获取的资源(数据库连接、事务、已验证的会话、文件句柄等)应在工作完成时进行清除。.NET 提供了 IDisposable
接口来帮助实现这种更确定的清除方式。
对于某些 IoC 容器,需要明确地告诉它们清除某个特定实例,例如通过 ReleaseInstance()
方法。这使得确保使用正确的清除语义变得非常困难。
- 从非清除组件切换到清除组件可能需要修改客户端代码。
- 使用共享实例时可能会忽略清除操作的客户端代码,在切换到非共享实例时几乎肯定会忘记清理。
Autofac 通过生命周期范围 解决这些问题,以便在单个工作单元中清除所有创建的组件。
using (var scope = container.BeginLifetimeScope())
{
scope.Resolve<DisposableComponent>().DoSomething();
// 当 `using` 语句结束,即当自身被清除时,范围内的所有组件都会被清除。
}
在工作单元开始时创建一个生命周期范围,当工作单元完成时,嵌套容器可以清除范围内所有超出作用域的实例。
注册组件
虽然 Autofac
可以自动清除一些组件,但您也可以手动指定清除机制。
组件必须注册为 InstancePerDependency()
(默认)或 InstancePerLifetimeScope()
的某种变体(如 InstancePerMatchingLifetimeScope()
或 InstancePerRequest()
)。
如果您注册了单例组件(作为 SingleInstance()
),它们将随着容器的存在而存在。由于容器的生命周期通常与应用程序生命周期相同,这意味着组件将在应用程序结束时才被清除。
自动清除
要利用自动确定性的清除,您的组件必须实现 IDisposable
。然后,您可以根据需要注册组件,并在每次使用生命周期范围解析组件后,组件的 Dispose()
方法会被调用。
var builder = new ContainerBuilder();
builder.RegisterType<SomeDisposableComponent>();
var container = builder.Build();
// 创建嵌套生命周期范围,解析组件并清除范围。
// 组件将随范围一起清除。
异步清除支持
如果您的组件的清除行为需要一些 I/O 操作,例如将缓冲区的内容写入文件或将数据包发送到网络以关闭连接,那么您可能需要考虑实现新的 .NET 接口 IAsyncDisposable。
在 Autofac 5.0 中,增加了对 IAsyncDisposable
接口的支持,现在可以异步清除生命周期范围:
class MyComponent : IDisposable, IAsyncDisposable
{
INetworkResource myResource;
public void Dispose()
{
myResource.Close();
}
public async ValueTask DisposeAsync()
{
await myResource.CloseAsync();
}
}
// ...
await using (var scope = container.BeginLifetimeScope())
{
var service = scope.Resolve<MyComponent>();
// 当 `using` 块退出时,`DisposeAsync` 将被调用到 `MyComponent` 上。
}
当一个生命周期范围被异步清除时,任何注册的实现了 IAsyncDisposable
的组件除了 IDisposable
还会调用 DisposeAsync()
方法,而不是 Dispose()
方法。
如果组件仅实现了同步的 Dispose()
方法,则在异步清除生命周期范围时仍会调用它。
当使用 Autofac
与 ASP.NET Core 集成时,所有按请求的生命周期范围都会被异步清除。
尽管实现
IAsyncDisposable
不强制要求实现 IDisposable
,但我们强烈建议您这样做。如果您的组件只实现了 IAsyncDisposable
,但有人同步清除范围,Autofac 将被迫使用同步阻塞的清除,会发出一个诊断警告:
AUTOFAC: 试图进行同步清除,但跟踪的对象类型为
AsyncComponent
只实现了IAsyncDisposable
。这会导致效率低下的阻塞清除。考虑在AsyncComponent
上实现IDisposable
,或者使用DisposeAsync
来清除范围/容器。
指定清除
如果您的组件不实现 IDisposable
,但在生命周期范围结束时仍需要一些清理操作,您可以使用 OnRelease
生命周期事件 。
var builder = new ContainerBuilder();
builder.RegisterType<SomeComponent>()
.OnRelease(instance => instance.CleanUp());
var container = builder.Build();
// 创建嵌套生命周期范围,解析组件并清除范围。
// 当范围被清除时,组件的 "CleanUp()" 方法将被调用。
请注意,OnRelease()
覆盖了 IDisposable.Dispose()
的默认处理。如果您的组件同时实现了 IDisposable
和需要其他清理方法,您可能需要在 OnRelease()
中手动调用 Dispose()
,或者更新您的类,使其在 Dispose()
中调用清理方法。
禁用清除
默认情况下,组件由容器所有并会在适当的时候清除。要禁用此行为,请将组件注册为外部所有:
builder.RegisterType<SomeComponent>().ExternallyOwned();
容器永远不会调用外部所有注册对象的 Dispose()
或 DisposeAsync()
。清除此类组件的责任在于您。
另一种禁用清除的替代方案是使用隐式关系 Owned<T>
和 拥有实例 。在这种情况下,您在消费代码中不再依赖类型 T
,而是依赖 Owned<T>
。消费代码将负责清除。
public class Consumer
{
private Owned<DisposableComponent> _service;
public Consumer(Owned<DisposableComponent> service)
{
_service = service;
}
public void DoWork()
{
// _service 用于一些任务
_service.Value.DoSomething();
// 当 _service 不再需要时,将其释放
_service.Dispose();
}
}
有关 Owned<T>
的更多信息,请参阅 拥有实例 话题。
从生命周期范围解析组件
生命周期范围通过调用 BeginLifetimeScope()
创建。最简单的方法是在 using
块中。使用生命周期范围来解析您的组件,然后在工作单元完成时清除范围。
using (var lifetime = container.BeginLifetimeScope())
{
var component = lifetime.Resolve<SomeComponent>();
// 组件及其所有可清除的依赖项将在 `using` 块完成时被清除。
}
注意,在 Autofac
集成库 中,标准的工作单元生命周期范围会自动为您创建和清除。例如,在 Autofac
的 ASP.NET MVC 集成 中,每个 Web 请求开始时会为您创建一个生命周期范围,通常会从那里解析所有组件。在 Web 请求结束时,范围会自动清除,无需您额外创建范围。如果您正在使用集成库 ,请了解可用的自动创建范围。
您可以在此处了解更多关于 创建生命周期范围的信息 。
子范围不会自动清除
尽管生命周期范围本身实现了 IDisposable
,但您创建的生命周期范围 不会自动为您清除。如果您创建了一个生命周期范围,您负责调用 Dispose()
清理它并触发组件的自动清除。这可以通过 using
语句轻松完成,但如果在没有 using
的情况下创建范围,别忘了在完成时清除它。
重要的是区分 您创建的范围 和 集成库为您创建的范围。您无需管理集成范围(如 ASP.NET 请求范围),这些将为您处理。然而,如果您手动创建自己的范围,将负责清理它。
提供的实例
如果您向 Autofac
提供 实例注册 ,Autofac
将假设对实例的所有权,并处理其清除。
// 如果这样操作,`Autofac` 将在容器清除时清除 StringWriter 实例。
var output = new StringWriter();
builder.RegisterInstance(output)
.As<TextWriter>();
如果您想自己控制实例的清除,需要将实例注册为 ExternallyOwned()
。
// 使用 ExternallyOwned 表明您将负责清除 StringWriter,而不是 `Autofac`。
var output = new StringWriter();
builder.RegisterInstance(output)
.As<TextWriter>()
.ExternallyOwned();
更高级别的层次结构
上述演示的最简单且最推荐的资源管理场景是两层的:有一个单一的 “根” 容器,并为每个工作单元创建一个生命周期范围。然而,可以使用 标记生命周期范围 创建更复杂的容器和组件层次结构。