项目使用了 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
,目标类型实例化总是先从该字典获取委托。
方法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()
;
CreateInstanceCallSite
作为实例化上下文;CreateArgumentCallSites()
遍历所有参数,递归创建各个参数的 IServiceCallSite
实例,得到数组。接着使用前一步得到的数组作为参数, 创建出 ConstructorCallSite
实例。泛型、集合处理多了部分前置工作,在此略过。
如下流程图简要地展示了递归过程:
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
维护的字典;ServiceProviderEngine
内部类型为CallSiteRuntimeResolver
的成员完成目标服务的实例化;CompiledServiceProviderEngine
内部类型为ExpressionResolverBuilder
成员的方法Resolve()
得到委托,替换前述的ServiceProviderEngine
维护的字典内容。委托的前2次执行结果总是由
ServiceProviderEngine.RuntimeResolver
返回的。
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
中;CallSiteRuntimeResolver
完成目标服务的实例化;GetService()
时,
ExpressionResolverBuilder
使用表达式树重新生成委托,并操作字典RealizedServices
,替换初次调用生成委托;GetService()
时,字典RealizedServices
查找到的是已经替换过的使用表达式树生成的委托。没有线程安全问题,委托一定会被替换,视表达式树的构建完成时机。
Microsoft.Extensions.DependencyInjection 2.x 希望在开销和性能中取得平衡,其实现方式是使用特殊委托完成委托本身的替换。``CallSiteVisitor` 是获取实例和表示式树的核心实现。
由于表达式创建的过程中不存在对参数的表达式树的缓存过程,故对于 A 依赖 B 的情况,如果只是获取 A ,使得 A 的表达式树构建完成并以委托形式缓存,单独获取 B 仍然要完成先反射后构造表达式的流程,见 CompiledServiceProviderEngine。
掌握了 Microsoft.Extensions.DependencyInjection 2.x 的实现机制,加上对内存 dump 的对比,已经知道表达式树的构建过程是产生开销的原因,出于篇幅控制另行展开。
leoninew 原创,转载请保留出处 www.cnblogs.com/leoninew