解析 Microsoft.Extensions.DependencyInjection 2.x 版本实现
项目使用了 Microsoft.Extensions.DependencyInjection 2.x 版本,遇到第2次请求时非常高的内存占用情况,于是作了调查,本文对 3.0 版本仍然适用。
先说结论,可以转到ServiceProvider章节,为了在性能与开销中获取平衡,Microsoft.Extensions.DependencyInjection在初次请求时使用反射实例化目标服务,再次请求时异步使用表达式树替换了目标实例化委托,使得后续请求将得到性能提升。
IServiceProviderEngine
依赖注入的核心是IServiceProviderEngine,它定义了GetService()方法,再被IServiceProvider间接调用。
IServiceProviderEngine包含若干实现,由ServiceProvider的构造函数的参数决定具体的实现类型。由于ServiceProviderOptions.Mode是内部可见枚举,默认值为ServiceProviderMode.Dynamic,ServiceCollectionContainerBuilderExtensions.BuildServiceProvider()作为入口没有控制能力,使得成员_engine是类型为DynamicServiceProviderEngine的实例。
最终实现类DynamicServiceProviderEngine从CompiledServiceProviderEngine继承,后者再从抽象类ServiceProviderEngine继承。
抽象类ServiceProviderEngine定义了方法GetService(Type serviceType),并维护了默认可见性的线程安全的字典internal ConcurrentDictionary> RealizedServices,目标类型实例化总是先从该字典获取委托。
User
IServiceCollection
ServiceProvider
DynamicServiceProviderEngine
CompiledServiceProviderEngine
ServiceProviderEngine
AddTransient
BuildServiceProvider()
ServiceProvider
new
base
base
GetService()
GetService()
this.RealizedServices.GetOrAdd()
internal
TService
TService
User
IServiceCollection
ServiceProvider
DynamicServiceProviderEngine
CompiledServiceProviderEngine
ServiceProviderEngine
方法ServiceProviderEngine.GetService()并不是抽象方法,上述两个个实现类也没有重写。方法被调用时,ServiceProviderEngine的私有方法CreateServiceAccessor(Type serviceType)首先使用CallSiteFactory分析获取待解析类型的上下文IServiceCallSite,接着调用子类的RealizeService(IServiceCallSite)实现。
ServiceProviderEngine
这里解析两个重要依赖CallSiteFactory和CallSiteRuntimeResolver,以及数据结构IServiceCallSite,前两者在ServiceProviderEngine的构造函数中得到实例化。
CallSiteFactory
ServiceProviderEngine以注入方式集合作为构建函数的参数,但参数被立即转交给了CallSiteFactory,后者在维护注入方式集合与了若干字典对象。
List _descriptors:所有的注入方式集合
Dictionary _callSiteCache:目标服务类型与其实现的上下文字典
Dictionary _descriptorLookup:使用目标服务类型分组后注入方式映射
ServiceDescriptorCacheItem是维护了List的结构体,CallSiteFactory总是使用最后一个注入方式作为目标类型的实例化依据。
IServiceCallSite
IServiceCallSite是目标服务类型实例化的上下文,CallSiteFactory通过方法CreateCallSite()创建IServiceCallSite,并通过字典_callSiteCache进行缓存。
首先尝试调用针对普通类型的TryCreateExact()方法;
如果前一步为空,接着尝试调用针对泛型类型的TryCreateOpenGeneric()方法;
如果前一步为空,继续深度调用针对可枚举集合的 TryCreateEnumerable()方法;
TryCreateEnumerable()内部使用了TryCreateExact()和TryCreateOpenGeneric()
CallSiteFactory对不同注入方式有选取优先级,优先选取实例注入方式,其次选取委托注入方式,最后选取类型注入方式,以 TryCreateExact()为例简单说明:
对于使用单例和常量的注入方式,返回ConstantCallSite实例;
对于使用委托的注入方式,返回FactoryCallSite实例;
对于使用类型注入的,CallSiteFactory调用方法CreateConstructorCallSite();
如果只有1个构造函数
无参构造函数,使用 CreateInstanceCallSite作为实例化上下文;
有参构造函数存,首先使用方法CreateArgumentCallSites()遍历所有参数,递归创建各个参数的 IServiceCallSite 实例,得到数组。接着使用前一步得到的数组作为参数, 创建出 ConstructorCallSite实例。
如果多于1个构造函数,检查和选取最佳构造函数再使用前一步逻辑处理;
最后添加生命周期标识
泛型、集合处理多了部分前置工作,在此略过。
如下流程图简要地展示了递归过程:
CallSiteRuntimeResolver
CallSiteRuntimeResolver从CallSiteVisitor继承,被抽象类ServiceProviderEngine依赖,被DynamicServiceProviderEngine间接引用。
由于目标服务类型实例化上下文已经由CallSiteFactory获取完成,该类的工作集中于类型推断与调用合适的方法实例化取目标服务。
ConstantCallSite:获取引用的常量;
FactoryCallSite:执行委托;
CreateInstanceCallSite:反射调用Activator.CreateInstance();
ConstructorCallSite:递归实例化各个参数得到数组,接着作为参数反射调用ConstructorInfo.Invoke() ;
前面提到ServiceProviderEngine维护了字典,用于该委托的存取,后面继续会讲到。
ServiceProviderEngine.GetService()内部使用其私有方法CreateServiceAccessor(),传递CallSiteFactory获取到IServiceCallSite实例到子类重写的方法RealizeService(),故关注点回到DynamicServiceProviderEngine。
DynamicServiceProviderEngine
DynamicServiceProviderEngine重写父类方法RealizeService(),返回了一个特殊的委托,委托内包含了对父类CompiledServiceProviderEngine和抽象类ServiceProviderEngine的成员变量的调用。
该委托被存储到ServiceProviderEngine维护的字典;
该委托被第1次调用时,使用ServiceProviderEngine内部类型为CallSiteRuntimeResolver的成员完成目标服务的实例化;
该委托被第2次调用时,除了第1步外,额外另起 Task 调用父类CompiledServiceProviderEngine内部类型为ExpressionResolverBuilder成员的方法Resolve()得到委托,替换前述的ServiceProviderEngine维护的字典内容。
委托的前2次执行结果总是由ServiceProviderEngine.RuntimeResolver返回的。
ServiceProvider
ServiceProviderEngine
CallSiteFactory
DynamicServiceProviderEngine
CompiledServiceProviderEngine
GetService()
RealizedServices.GetOrAdd()
CreateServiceAccessor(Type serviceType)
CreateCallSite(Type serviceType)
IServiceCallSite
context of TService
RealizeService(IServiceCallSite)
Task.Run(() => base.RealizeService())
alt
[ Interlocked.Increment(ref callCount) == 2 ]
base.RuntimeResolver.Resolve()
Func
delegate
execute delegate with scope
TService
ServiceProvider
ServiceProviderEngine
CallSiteFactory
DynamicServiceProviderEngine
CompiledServiceProviderEngine
CompiledServiceProviderEngine
CompiledServiceProviderEngine依赖了ExpressionResolverBuilder,并操作了抽象类ServiceProviderEngine维护的字典对象RealizedServices。
ExpressionResolverBuilder
ExpressionResolverBuilder从CallSiteVisitor继承,正如其名是表达式树的相关实现,其方法Build()构建和返回类型为Func的委托。
ExpressionResolverBuilder和 CallSiteRuntimeResolver一样继承了抽象类CallSiteVisitor,所以解析出表达式树的过程极其相似,根据 IServiceCallSite创建出表达式树。
ConstantCallSite:使用Expression.Constant();
FactoryCallSite:使用Expression.Invocation();
CreateInstanceCallSite:使用 Expression.New();
ConstructorCallSite:递归创建各个参数的表达式树得到数组,接着作为参数,使用Expression.New() 创建最终的表达式树;
ServiceProvider
回顾整个流程可知,CallSiteFactory、CallSiteRuntimeResolver、ExpressionResolverBuilder是目标服务实例化的核心实现:
CallSiteFactory:解析和缓存目标服务的实例化上下文;
CallSiteRuntimeResolver:使用反射完成目标服务的实例化;
ExpressionResolverBuilder:使用表达式树得到目标服务的实例化的前置委托;
ServiceProvider通过特殊的委托完成了目标服务实例化方式的替换:
初次调用GetService()时
首先通过DynamicServiceProviderEngine返回了委托,该委托被存储到字典 RealizedServices中;
接着该委托被第1次执行,通过CallSiteRuntimeResolver完成目标服务的实例化;
再将调用GetService()时,
直接得到缓存的委托并同样完成目标服务的实例
同时通过一个额外的 Task,通过ExpressionResolverBuilder 使用表达式树重新生成委托,并操作字典RealizedServices,替换初次调用生成委托;
后续调用GetService()时,字典RealizedServices查找到的是已经替换过的使用表达式树生成的委托。
没有线程安全问题,委托一定会被替换,视表达式树的构建完成时机。
ServiceProviderEngine
DynamicServiceProviderEngine
CompiledServiceProviderEngine
ExpressionResolverBuilder
CallSiteRuntimeResolver
RealizeService(IServiceCallSite)
Task.Run(() => base.RealizeService(IServiceCallSite))
Build(IServiceCallSite)
ExpressionTree
func: Func
base.RealizedServices[callSite.ServiceType] = func
Rewrite cache
alt
[ Interlocked.Increment(ref callCount) == 2 ]
base.RuntimeResolver.Resolve(IServiceCallSite)
Resole(IServiceCallSite, scope)
Reflection
Func
delegate
delegate
execute delegate with scope
ServiceProviderEngine
DynamicServiceProviderEngine
CompiledServiceProviderEngine
ExpressionResolverBuilder
CallSiteRuntimeResolver
Summary
Microsoft.Extensions.DependencyInjection 2.x 希望在开销和性能中取得平衡,其实现方式是使用特殊委托完成委托本身的替换。``CallSiteVisitor` 是获取实例和表示式树的核心实现。
由于表达式创建的过程中不存在对参数的表达式树的缓存过程,故对于 A 依赖 B 的情况,如果只是获取 A ,使得 A 的表达式树构建完成并以委托形式缓存,单独获取 B 仍然要完成先反射后构造表达式的流程,见 CompiledServiceProviderEngine。
掌握了 Microsoft.Extensions.DependencyInjection 2.x 的实现机制,加上对内存 dump 的对比,已经知道表达式树的构建过程是产生开销的原因,出于篇幅控制另行展开。
leoninew 原创,转载请保留出处 www.cnblogs.com/leoninew
好文要顶 关注我 收藏该文
leoninew
关注 - 0
粉丝 - 0
+加关注
1 0
posted @ 2019-11-01 09:33 leoninew 阅读(17) 评论(0) 编辑 收藏
https://www.cnblogs.com/leoninew/p/DependencyInjection.html