【问题标题】:Automatically dispose child lifetime scopes in Autofac在 Autofac 中自动处理子生命周期范围
【发布时间】:2021-10-27 18:12:21
【问题描述】:

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

子范围不会自动处理

虽然生命周期范围本身实现 IDisposable,但您创建的生命周期范围不会自动为您处置。如果你创建了一个生命周期范围,你有责任在它上面调用Dispose()来清理它并触发组件的自动处理。

但是,在这种情况下,我确实希望自动处置孩子。

到目前为止,我想到的解决方法是让子范围处置父范围。像这样的:

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

由于我从未跟踪过scope,只有childScope,我需要一种方法来处置父级。

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

我不确定我是否正确地处理了这个问题,所以我想看看这里是否有更好的解决方案。我正在将我的代码库从 UnityContainer 迁移到 Autofac。之前的代码有一个 UnityDisposer 对象,它会从上到下遍历父/子树并处理所有内容,但我没有使用 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

据我所知,对于线性父/子关系,这似乎可行。但这并不能解决多子问题。并且明确地说,我对线性情况的解决方案也不是特别满意。

【问题讨论】:

  • 我想知道这里是否存在冲突 - 当孩子活跃时处置父母是否有意义?那安全吗?
  • @DanielA.White 我也考虑过这一点,但由于父母不处置其子生命周期范围,我认为这使它安全。无论如何,您的问题与我的问题完全相同,也是本文的目的。
  • @void.pointer 为什么要更改变量scope?为什么不让它成为父母并与childScope 一起工作呢?但更重要的是:为什么不直接打电话给scope.Disposer.AddInstanceForDisposal(childScope);?这样子作用域就会在父作用域被释放时被释放。
  • 因为我需要使用子范围来访问它的注册。父范围仅用于一个目的:提供注册的基本子集。我的代码库只对只能从孩子访问的超集感兴趣。而且因为我对跟踪 parent 不感兴趣,这意味着我需要孩子确保它被处置。
  • @void.pointer 什么时候应该处置孩子?

标签: c# autofac


【解决方案1】:

从 Autofac 的角度来看,您实际上必须开始跟踪父作用域和子作用域,以便您可以选择何时处置它们。这有点像蜘蛛侠的情况:权力越大,责任越大。

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

把父母从孩子的下面赶出去是个坏主意。除了一旦父母被处置,您就无法真正解决孩子的问题(是的,我知道父母会以某种方式坚持到最后一个孩子结束,但事实仍然存在);我们确实看到过一些奇怪的边缘情况,人们有意或无意地将父母从孩子的下方丢弃,并且取决于它是如何连接的以及事情发生的顺序,你最终可能会很难排除故障涉及ObjectDisposedException 的异常。

如果您考虑在 Autofac 上下文之外 尝试做什么,那么尝试让系统自行处置是一种糟糕的设计模式,就像以某种方式拥有子作用域一样对父母负责。例如,如果您想要执行此操作的其他父/子关系,则更常见的是,您会看到某种处理引用计数和处置的外部协调器 - 在逻辑父/子关系之外。

我建议改为这样做 - 实际跟踪具有某种编排器类性质的父作用域和子作用域,并让编排器监视所有子作用域消失。

另外,它还会让其他情况变得更容易,例如“我创建了父级,我创建了一个子级,当我试图创建第二个子级时,它被丢弃并清理了父级。”穿线的东西。您将能够在您的编排器代码中考虑到这一点。

【讨论】:

  • 我查看了 Autofac 代码。在调用 CurrentScopeEnding 事件后立即处置处置器。因此,从语义上讲,在这些位置中的任何一个放置父级都不会改变这里的问题。并且编排器需要依赖 CurrentScopeEnding 回调来处理父级。安全地做到这一点的唯一方法是在孩子被处置后将父母的处置“排队”,但这既复杂又令人讨厌。 LifetimeScope.Dispose() 代码并不表示在处置孩子时处置父母存在问题。
  • 话虽如此,编排器实现是我使用的地方,它更干净,编排器本身设置为InstancePerLifetimeScope(),因此每个“父”容器都拥有一个,它跟踪子容器作用域,并监视它们的结束,然后处置其拥有的作用域。正如您所说,该解决方案更加灵活,即使它代表了一种反模式......
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-11-24
  • 1970-01-01
  • 2014-09-19
  • 1970-01-01
相关资源
最近更新 更多