项目
版本

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 集成库 中,标准的工作单元生命周期范围会自动为您创建和清除。例如,在 AutofacASP.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();

更高级别的层次结构

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

在本文档中