项目
版本

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();

更高级别的层次结构

上述演示的最简单且最推荐的资源管理场景是两层的:有一个单一的 “根” 容器,并为每个工作单元创建一个生命周期范围。然而,可以使用 标记生命周期范围 创建更复杂的容器和组件层次结构。

在本文档中