在 Autofac 中自动处理 child 个生命周期范围

Automatically dispose child lifetime scopes in Autofac

有很多次我在 Autofac 中创建一个 child 生命周期范围只是为了替换或补充 parent 中的注册。从那时起,我只使用 child 生命周期范围。 autofac 文档指出 child scopes are not automatically disposed:

Child Scopes are NOT Automatically Disposed

While lifetime scopes themselves implement IDisposable, the lifetime scopes that you create are not automatically disposed for you. If you create a lifetime scope, you are responsible for calling Dispose() on it to clean it up and trigger the automatic disposal of components.

然而,在这种情况下,我确实希望 children 被自动处理。

到目前为止我想到的解决方法是让 child 作用域处理 parent。像这样:

ILifetimeScope scope = ...;
var childScope = scope.BeginLifetimeScope();
childScope.Disposer.AddInstanceForDisposal(scope);
scope = childScope;

因为我从来没有跟踪过 scope,只有 childScope,所以我需要一种方法来处理 parent。

更复杂的是,每个 parent 可以有多个 child 作用域。所以在那些情况下我不能这样做。我只想在处理最后一个 child 时处理 parent。为此,我认为在 parent 中我必须在每次调用 BeginLifetimeScope() 时注册一些专用于引用计数的服务,并在处理 child 时减少该引用计数。

我不确定我是否正确地处理了这个问题,所以我想看看这里是否有更好的解决方案。我正在将我的代码库从 UnityContainer 迁移到 Autofac。该代码以前有一个 UnityDisposer object 会从上到下遍历 parent/child 树并处理所有内容,但我没有使用 Autofac 得到那种层次结构。

编辑

对我推测的解决方案存在一些疑问,因此我编写了一个示例应用程序以查看会发生什么:

class ThingA : IDisposable
{
    public ThingA()
    {
        Console.WriteLine("Construct ThingA");
    }

    public void Dispose()
    {
        Console.WriteLine("Dispose ThingA");
    }
}

class ThingB : IDisposable
{
    public ThingB()
    {
        Console.WriteLine("Construct ThingB");
    }

    public void Dispose()
    {
        Console.WriteLine("Dispose ThingB");
    }
}

public static class Program
{
    public static void Main()
    {
        ContainerBuilder builder;

        builder = new ContainerBuilder();
        builder.RegisterType<ThingA>().InstancePerLifetimeScope();

        ILifetimeScope container1 = builder.Build();
        container1.Resolve<ThingA>();

        var container2 = container1.BeginLifetimeScope(builder2 =>
        {
            builder2.RegisterType<ThingB>().InstancePerLifetimeScope();
        });

        container2.Disposer.AddInstanceForDisposal(container1);
        container1 = container2;

        container1.Resolve<ThingB>();

        container1.Dispose();
    }
}

我得到的输出是:

Construct ThingA
Construct ThingB
Dispose ThingB
Dispose ThingA

据我所知,对于线性 parent/child 关系,这似乎有效。但它并没有解决 multiple-child 问题。明确地说,我对线性情况的解决方案也不是特别满意。

从 Autofac 的角度来看,您实际上必须开始跟踪 parent 和 child 范围,以便您可以选择何时处置它们。这是一种 Spider-Man 情况:能力越大,责任越大。

有时这就像用 using 语句包装各种工作单元一样简单。在异步情况下,例如 ASP.NET,中间件和 HttpContext 等地方开始发挥作用。

将 parent 从 child 下移出不是个好主意。除了 parent 被处理后你无法真正解决 child 的问题(是的,我知道 parent 会以某种方式坚持到最后一个 child,但事实依然如此);我们肯定已经看到人们通过有意或无意地将 parent 从 child 下移除而陷入奇怪的边缘情况,并且根据它的连接方式和事情发生的顺序,你最终可能会导致涉及 ObjectDisposedException.

的异常难以排除

如果您考虑要在 Autofac 上下文 之外 做什么,尝试让系统自行处理是一种糟糕的设计模式,例如让 child 范围以某种方式负责 parent。例如,如果您有一些其他 parent/child 关系想要执行此操作,您会经常看到某种处理引用计数和处置的外部协调器 - 在逻辑 parent/child 关系之外。

我建议改为做类似的事情 - 实际跟踪 parent 和 child 具有协调器 class 和某些性质的范围让协调器监视所有 child 范围消失。

此外,它会让其他情况变得更容易,比如“我创建了 parent,我创建了一个 child,它在我创建时被处理并清理了 parent试图创建第二个 child。”线程的东西。您将能够在您的协调程序代码中考虑到这一点。