项目
版本

Autofac 池化实例

通常,应用程序会发现它们有昂贵的初始化组件(比如数据库或某种外部服务连接),而你更希望复用这些实例,而不是每次需要时都创建新的。

这通常被称为维护一个“对象池”。当你想要一个对象的新实例时,从池中获取一个,使用完毕后,再将其返回到池中。

Autofac 可以帮助你在应用程序中实现对象池,而无需自己编写池化实现,让这些池化的组件在依赖注入的世界中感觉更加自然。

注意
在继续之前,值得一提的是,许多.NET 标准类型(如 HttpClient 或 ADO.NET 的 SqlConnection )已经在幕后为你实现了池化,因此在这些类型上添加 Autofac 池化没有任何益处。

开始使用

要开始创建 Autofac 的池化注册,首先请添加对 Autofac.Pooling NuGet 包 的引用。

有了这个,你可以开始定义和使用池化注册,使用新的生命周期配置方法 PooledInstancePerLifetimeScopePooledInstancePerMatchingLifetimeScope

var builder = new ContainerBuilder();

builder.RegisterType<MyCustomConnection>()
       .As<ICustomConnection>()
       .PooledInstancePerLifetimeScope();

var container = builder.Build();

using (var scope = container.BeginLifetimeScope())
{
    // 创建MyCustomConnection的新实例
    var instance = scope.Resolve<ICustomConnection>();

    instance.DoSomething();
}

// 当生命周期范围结束时,MyCustomConnection实例将返回到池中,而不是被丢弃。

using (var scope2 = container.BeginLifetimeScope())
{
    // 不会创建新实例,而是从池中获取先前的实例。
    var instance = scope.Resolve<ICustomConnection>();

    instance.DoSomething();
}

// 当生命周期范围结束时,实例再次返回到池中。

像任何其他依赖项一样,你可以使用这些服务在构造函数中注入池化的实例:

public class WorkOperation
{
    // 注入池中的一个实例。
    public WorkOperation(ICustomConnection customConnection)
    {
        // ...
    }
}

当前的 生命周期范围 结束时,从池中获取的实例将返回到池中。

在解析之间重置池化实例

对于池化的组件,通常需要在从池中获取或返回到池时进行一些工作来重置对象。

Autofac 允许组件在从池中获取或返回到池时知道这一点,通过实现 IPooledComponent 接口:

public class PoolAwareComponent : IPooledComponent
{
    public void OnGetFromPool(IComponentContext context, IEnumerable<Parameter> parameters)
    {
        // 当组件在解析操作期间从池中获取时(包括第一次使用时)被调用。
    }

    public void OnReturnToPool()
    {
        // 当组件即将返回到池时被调用。
    }
}

OnGetFromPool 方法会传递当前解析操作的临时 IComponentContext ,以及传递给解析的所有参数。

警告
从提供的 IComponentContext 中解析的任何服务将来自 当前访问池化组件的作用域。这意味着在 OnReturnToPool 中应该丢弃从该 IComponentContext 中解析的任何实例,以防止内存泄漏。

如果你不能修改你要池化的组件,但需要类似这样的自定义行为,你可以 实现自定义池策略

池容量

每个池化注册都有池容量的概念。默认值为 Environment.ProcessorCount * 2 ,但可以很容易地使用扩展方法的重载进行自定义:

// 设置容量为100
builder.RegisterType<MyCustomConnection>()
        .As<ICustomConnection>()
        .PooledInstancePerLifetimeScope(100);

重要的是要理解池的容量不会限制它分配/激活的实例数量,或者在任何时候可以使用的实例数量;相反,它限制了池保留的实例数量

在实际情况下,这意味着如果你的池容量为 100,而你当前有 100 个实例在使用,那么解析另一个实例将只是激活组件的新实例,而不是阻塞或失败。

然而,如果你有 101 个该组件的实例在使用,下一个返回到池中的实例将被丢弃,而不是保留。在这种情况下,IPooledComponent 上的 OnReturnToPool 方法仍然会被调用,然后实例会被立即丢弃。

当池丢弃一个实例时,如果对象实现了 IDisposableDispose 将被调用。

如果你确实希望你的池具有阻止资源可用直到获得的行为,你可以 实现自定义池策略

注意
Autofac 池化行为建立在 Microsoft.Extensions.ObjectPool 库提供的 对象池 实现之上。
该池的行为影响了许多 Autofac.Pooling 的行为。

匹配生命周期范围

与配置常规注册以 匹配生命周期范围 的方式相同,也可以配置池化注册以以相同的方式进行匹配:

builder.RegisterType<MyCustomConnection>()
       .As<ICustomConnection>()
       .PooledInstancePerMatchingLifetimeScope("tag");

具有匹配生命周期范围的池化注册会导致每个标记的范围从池中获取自己的实例,子范围共享相同的池化实例。

当标记的生命周期范围被丢弃时,实例将返回到池中。

池策略

如果你需要在实例从池中获取或返回到池时执行一些自定义行为,你可以实现 IPooledRegistrationPolicy<TPooledObject> ,或者覆盖 DefaultPooledRegistrationPolicy<TPooledObject>

以下是一个简单的策略示例,它会在可用容量耗尽时阻止进一步请求池化的实例:

public class BlockingPolicy<TPooledObject> : IPooledRegistrationPolicy<TPooledObject>
    where TPooledObject : class
{
    private readonly SemaphoreSlim _semaphore;

    public BlockingPolicy(int maxConcurrentInstances)
    {
        // 使用实例的数量创建一个信号量。
        _semaphore = new SemaphoreSlim(maxConcurrentInstances);

        // 指定池应保留指定的并发实例数。
        MaximumRetained = maxConcurrentInstances;
    }

    /// <summary>
    /// 获取池中保留的最大项数。
    /// </summary>
    public int MaximumRetained { get; }

    /// <summary>
    /// 当请求<typeparamref name="TPooledObject"/>实例时被调用。策略可以调用<paramref name="getFromPool"/>从池中检索实例。同样,它可以选择忽略池,只返回一个自定义实例。
    /// </summary>
    /// <param name="context">当前组件上下文。</param>
    /// <param name="parameters">访问池的解析请求的参数集。</param>
    /// <param name="getFromPool">回调,将从底层对象池中检索一个项。</param>
    public TPooledObject Get(IComponentContext context, IEnumerable<Parameter> parameters, Func<TPooledObject> getFromPool)
    {
        // 在尝试从池中检索实例之前,先阻塞信号量。
        _semaphore.Wait();

        // 从池中返回一个实例(如果需要,会创建一个新的)。
        return getFromPool();
    }

    /// <summary>
    /// 当一个对象即将返回到池中时被调用。
    /// </summary>
    /// <param name="pooledObject">池化对象。</param>
    /// <returns>
    /// 如果对象应该返回到池,则为true。如果不应将其放回池中(并且如果实现`IDisposable`,则会立即丢弃),则为false。
    /// </returns>
    public bool Return(TPooledObject pooledObject)
    {
        // 释放信号量以释放一个实例。
        _semaphore.Release();

        // 返回true,将对象放回池中,而不是丢弃它。
        return true;
    }
}

然后,你可以在注册池时使用此策略:

// 注册一个最多只允许100个并发实例的池。
builder.RegisterType<MyCustomConnection>()
        .As<ICustomConnection>()
        .PooledInstancePerLifetimeScope(new BlockingPolicy<MyCustomConnection>(100));
在本文档中