为什么 Autofac 的动态实例化隐式关系类型 (Func<B>) 遵守生命周期范围?

Why does Autofac's implicit relationship type for dynamic instantiation (Func<B>) respect lifetime scopes?

为什么 Autofac 的隐式关系类型 dynamic instantiation 尊重生命周期范围?


关于该主题的文档指出:

Lifetime scopes are respected using this relationship type. If you register an object as InstancePerDependency() and call the Func<B> multiple times, you’ll get a new instance each time. However, if you register an object as SingleInstance() and call the Func<B> to resolve the object more than once, you will get the same object instance every time.

文档还声称这种隐式关系类型的目的是为了这两个目的:

  1. 在运行时解析实例而不依赖于 Autofac 本身
  2. 解析给定服务的多个实例

Delayed instantiation (Lazy<B>) 以我理解的方式实现目的#1,我同意它的设计以尊重生命周期范围。我不理解也不同意 Autofac 对 Func<B> 的设计,因为无法使用动态实例化隐式关系类型来实现目的 #2。

要解决此问题并实际上完成#2,我们使用与注册 B 相同的工厂方法手动注册 Func<B> 和关闭:

void RegisterDependencies(ContainerBuilder builder)
{
    builder.Register<B>(bFactory).InstancePerLifetimeScope();
    builder.Register<Func<B>>(context => () => bFactory(context)).SingleInstance();

    B bFactory(IComponentContext context) => new B(context.Resolve<A>(), ...);
}

这可以实现动态实例化声称可以实现的两个目的,但要求我们对许多注册非常冗长。


有人可以阐明为什么 Autofac 设计其动态实例化的隐式关系类型以尊重生命周期范围吗?

提前谢谢你:)

知道 Func<B> 基本上是 "shortcut" for myCurrentLifetimeScope.Resolve<B> 包裹在非 Autofac 特定对象中,这可能有助于扭转这个问题并询问为什么 Func<B> 不会尊重生命周期范围,如果不尊重生命周期范围会产生什么后果

绝大多数使用生命周期范围的应用程序都是为了隔离工作单元。例如,具有按请求生命周期范围的 Web 应用程序。这是一个有趣的模式有很多重要的原因,比如能够只创建给定请求所需的资源,然后在请求完成时清理它们——只使用你需要的内存。

鉴于此,让我们这么说,一般来说,生命周期范围很有趣,如果不是跟踪和清理分配资源的好方法。

现在假设您连接到网络服务或其他东西。也许这是一个 WCF 服务。关于 WCF 服务客户端,您可能还记得一些事情:

  • 渠道一旦出问题,客户就一文不值。您不能只创建一个客户端,因为如果服务在您的通道上出现故障,您必须丢弃该客户端实例并创建一个新的。
  • 您需要在完成后处理客户端。如果您不这样做,他们可以保持频道畅通并耗尽资源。

鉴于此,Func<IServiceClient> 种关系变得非常有趣。

假设您有一个 MVC 控制器。这是一种伪代码,我不会通过编译器 运行 它。

public class MyController : Controller
{
  private readonly Func<IServiceClient> clientFactory;
  public MyController(Func<IServiceClient> clientFactory)
  {
    this._clientFactory = clientFactory;
  }

  public string GetSomethingReallyCool()
  {
    var retryCount = 0;
    while(retryCount < 5)
    {
      var client = this._clientFactory();
      try
      {
        return client.GetSomethingCool();
      }
      catch
      {
        retryCount++;
      }
    }

    return "We failed to get the cool data.";
  }
}

我们在这里内置了一种穷人的重试,因为服务不稳定。我不推荐这个确切的代码,它只是快速和容易地演示这个概念。

舞台已经搭好了!现在,为了便于讨论,让我们假装这些关系不尊重生命周期范围,只是片刻。

网络请求期间会发生什么?

  • 您的 MVC 控制器从每个请求的生命周期范围中解析。
  • 控制器采用 Func<IServiceClient> 函数在需要时动态创建 WCF 服务客户端。
  • 控制器操作创建服务客户端并打开通道。此服务客户端位于 根容器中 这意味着在您的整个应用程序关闭之前它永远不会被释放。
  • 服务故障。通道出现故障,没有关闭。它只是有点闲逛......但你不能使用相同的服务客户端实例。那位干杯。
  • 控制器操作创建另一个 服务客户端并打开一个通道。此服务客户端 也存在于根容器 中并将永远分配。
  • 服务调用成功,返回值。但是当客户端超出范围时,它们是一次性的——容器正在处理处置,对吧?所以当地人不在范围内,但他们仍然被分配。

这是一个非常可怕的内存泄漏

恢复正常行为!装完了!

如果它遵守生命周期范围,它会像这样工作:

  • 您的 MVC 控制器从每个请求的生命周期范围中解析。
  • 控制器采用 Func<IServiceClient> 函数在需要时动态创建 WCF 服务客户端。
  • 控制器操作创建服务客户端并打开通道。此服务客户端 在请求生命周期范围内 这意味着它将在请求结束时被处理掉。
  • 服务故障。通道出现故障,没有关闭。它只是有点闲逛......但你不能使用相同的服务客户端实例。那位干杯。
  • 控制器操作创建另一个 服务客户端并打开一个通道。此服务客户端 也存在于请求范围 中,将在请求结束时处理。
  • 服务调用成功,返回值
  • 请求生命周期结束并处理客户端 - 关闭通道,释放资源。

没有内存泄漏,清理得很好。

另一种情况 - 假设您 在创建生命周期范围时注册东西

var builder = new ContainerBuilder();
builder.RegisterType<Alpha>();
var container = builder.Build();
using(var scope = container.BeginLifetimeScope(b => b.RegisterType<Beta>())
{
  var f = scope.Resolve<Func<Beta>>();

  // f is a Func<Beta> - call it, and you should get a B.
  // If Func<Beta> doesn't respect lifetime scopes, what
  // does this yield?
  var beta = f();
}

这实际上是 WebAPI 集成等事物的常见模式 - 请求消息进来,当创建请求生命周期范围时,请求消息动态注册到请求范围中。它在全球范围内不存在。

您可能会从其中的一些内容中看出,Func<B> 行为的唯一合乎逻辑的方式是遵守生命周期范围。如果它只是行不通没有。