为什么 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.
文档还声称这种隐式关系类型的目的是为了这两个目的:
- 在运行时解析实例而不依赖于 Autofac 本身
- 解析给定服务的多个实例
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>
行为的唯一合乎逻辑的方式是遵守生命周期范围。如果它只是行不通没有。
为什么 Autofac 的隐式关系类型 dynamic instantiation 尊重生命周期范围?
关于该主题的文档指出:
Lifetime scopes are respected using this relationship type. If you register an object as
InstancePerDependency()
and call theFunc<B>
multiple times, you’ll get a new instance each time. However, if you register an object asSingleInstance()
and call theFunc<B>
to resolve the object more than once, you will get the same object instance every time.
文档还声称这种隐式关系类型的目的是为了这两个目的:
- 在运行时解析实例而不依赖于 Autofac 本身
- 解析给定服务的多个实例
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>
行为的唯一合乎逻辑的方式是遵守生命周期范围。如果它只是行不通没有。