Microsoft.Extensions.DependencyInjection 之一:解析实现
[TOC]
前言
项目使用了 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<Type, Func<ServiceProviderEngineScope, object>> RealizedServices
,目标类型实例化总是先从该字典获取委托。
User-->>IServiceCollection: AddTransient<TService>
User->>+IServiceCollection: BuildServiceProvider()
IServiceCollection->>-User: ServiceProvider
ServiceProvider-->>DynamicServiceProviderEngine: new
DynamicServiceProviderEngine-->>CompiledServiceProviderEngine: base
CompiledServiceProviderEngine-->>ServiceProviderEngine: base
User->>+ServiceProvider: GetService<TService>()
ServiceProvider->>+ServiceProviderEngine: GetService()
ServiceProviderEngine->>ServiceProviderEngine: this.RealizedServices.GetOrAdd()
note right of ServiceProviderEngine: internal
ServiceProviderEngine->>-ServiceProvider: TService
ServiceProvider->>-User: TService
方法ServiceProviderEngine.GetService()
并不是抽象方法,上述两个个实现类也没有重写。方法被调用时,ServiceProviderEngine
的私有方法CreateServiceAccessor(Type serviceType)
首先使用CallSiteFactory
分析获取待解析类型的上下文IServiceCallSite
,接着调用子类的RealizeService(IServiceCallSite)
实现。
ServiceProviderEngine
这里解析两个重要依赖CallSiteFactory
和CallSiteRuntimeResolver
,以及数据结构IServiceCallSite
,前两者在ServiceProviderEngine
的构造函数中得到实例化。
CallSiteFactory
ServiceProviderEngine
以注入方式集合作为构建函数的参数,但参数被立即转交给了CallSiteFactory
,后者在维护注入方式集合与了若干字典对象。
List<ServiceDescriptor> _descriptors
:所有的注入方式集合Dictionary<Type, IServiceCallSite> _callSiteCache
:目标服务类型与其实现的上下文字典Dictionary<Type, ServiceDescriptorCacheItem> _descriptorLookup
:使用目标服务类型分组后注入方式映射
ServiceDescriptorCacheItem
是维护了List<ServiceDescriptor>
的结构体,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个构造函数,检查和选取最佳构造函数再使用前一步逻辑处理;
- 如果只有1个构造函数
- 最后添加生命周期标识
泛型、集合处理多了部分前置工作,在此略过。
如下流程图简要地展示了递归过程:
CallSiteRuntimeResolver
CallSiteRuntimeResolver
从CallSiteVisitor<ServiceProviderEngineScope, object>
继承,被抽象类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: GetService()
ServiceProviderEngine->>ServiceProviderEngine: RealizedServices.GetOrAdd()
ServiceProviderEngine->>ServiceProviderEngine: CreateServiceAccessor(Type serviceType)
%% Func<ServiceProviderEngineScope, object>
ServiceProviderEngine->>+CallSiteFactory: CreateCallSite(Type serviceType)
CallSiteFactory->>-ServiceProviderEngine: IServiceCallSite
note left of CallSiteFactory: context of TService
ServiceProviderEngine->>+DynamicServiceProviderEngine: RealizeService(IServiceCallSite)
alt Interlocked.Increment(ref callCount) == 2
DynamicServiceProviderEngine-->>CompiledServiceProviderEngine: Task.Run(() => base.RealizeService())
end
DynamicServiceProviderEngine->>+CompiledServiceProviderEngine: base.RuntimeResolver.Resolve()
CompiledServiceProviderEngine->>-DynamicServiceProviderEngine: Func<ServiceProviderEngineScope, object>
DynamicServiceProviderEngine->>-ServiceProviderEngine: delegate
ServiceProviderEngine->>ServiceProviderEngine: execute delegate with scope
ServiceProviderEngine->>-ServiceProvider: TService
CompiledServiceProviderEngine
CompiledServiceProviderEngine
依赖了ExpressionResolverBuilder
,并操作了抽象类ServiceProviderEngine
维护的字典对象RealizedServices
。
ExpressionResolverBuilder
ExpressionResolverBuilder
从CallSiteVisitor<CallSiteExpressionBuilderContext, Expression>
继承,正如其名是表达式树的相关实现,其方法Build()
构建和返回类型为Func<ServiceProviderEngineScope, object>
的委托。
ExpressionResolverBuilder
和 CallSiteRuntimeResolver
一样继承了抽象类CallSiteVisitor<TArgument, TResult>
,所以解析出表达式树的过程极其相似,根据 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: RealizeService(IServiceCallSite)
alt Interlocked.Increment(ref callCount) == 2
DynamicServiceProviderEngine->>+CompiledServiceProviderEngine: Task.Run(() => base.RealizeService(IServiceCallSite))
CompiledServiceProviderEngine->>+ExpressionResolverBuilder: Build(IServiceCallSite)
note right of ExpressionResolverBuilder: ExpressionTree
ExpressionResolverBuilder->>-CompiledServiceProviderEngine: func: Func<ServiceProviderEngineScope, object>
CompiledServiceProviderEngine-->>ServiceProviderEngine: base.RealizedServices[callSite.ServiceType] = func
note right of ServiceProviderEngine: Rewrite cache
end
DynamicServiceProviderEngine->>+ServiceProviderEngine: base.RuntimeResolver.Resolve(IServiceCallSite)
ServiceProviderEngine->>+CallSiteRuntimeResolver: Resole(IServiceCallSite, scope)
note right of CallSiteRuntimeResolver: Reflection
CallSiteRuntimeResolver->>-ServiceProviderEngine: Func<ServiceProviderEngineScope, object>
ServiceProviderEngine->>-DynamicServiceProviderEngine: delegate
DynamicServiceProviderEngine->>-ServiceProviderEngine: delegate
ServiceProviderEngine->>ServiceProviderEngine: execute delegate with scope
Summary
Microsoft.Extensions.DependencyInjection 2.x 希望在开销和性能中取得平衡,其实现方式是使用特殊委托完成委托本身的替换。``CallSiteVisitor` 是获取实例和表示式树的核心实现。
由于表达式创建的过程中不存在对参数的表达式树的缓存过程,故对于 A 依赖 B 的情况,如果只是获取 A ,使得 A 的表达式树构建完成并以委托形式缓存,单独获取 B 仍然要完成先反射后构造表达式的流程,见 CompiledServiceProviderEngine。
掌握了 Microsoft.Extensions.DependencyInjection 2.x 的实现机制,加上对内存 dump 的对比,已经知道表达式树的构建过程是产生开销的原因,出于篇幅控制另行展开。
leoninew 原创,转载请保留出处 www.cnblogs.com/leoninew
Microsoft.Extensions.DependencyInjection 之一:解析实现的更多相关文章
- 解析 Microsoft.Extensions.DependencyInjection 2.x 版本实现
项目使用了 Microsoft.Extensions.DependencyInjection 2.x 版本,遇到第2次请求时非常高的内存占用情况,于是作了调查,本文对 3.0 版本仍然适用. 先说结论 ...
- 使用诊断工具观察 Microsoft.Extensions.DependencyInjection 2.x 版本的内存占用
目录 准备工作 大量接口与实现类的生成 elasticsearch+kibana+apm asp.net core 应用 请求与快照 Kibana 上的请求记录 请求耗时的分析 请求内存的分析 第2次 ...
- Microsoft.Extensions.DependencyInjection 之三:展开测试
目录 前文回顾 IServiceCallSite CallSiteFactory ServiceProviderEngine CompiledServiceProviderEngine Dynamic ...
- Microsoft.Extensions.DependencyInjection 之三:反射可以一战(附源代码)
目录 前文回顾 IServiceCallSite CallSiteFactory ServiceProviderEngine CompiledServiceProviderEngine Dynamic ...
- Microsoft.Extensions.DependencyInjection 之二:使用诊断工具观察内存占用
目录 准备工作 大量接口与实现类的生成 elasticsearch+kibana+apm asp.net core 应用 请求与快照 Kibana 上的请求记录 请求耗时的分析 请求内存的分析 第2次 ...
- DotNetCore跨平台~一起聊聊Microsoft.Extensions.DependencyInjection
写这篇文章的心情:激动 Microsoft.Extensions.DependencyInjection在github上同样是开源的,它在dotnetcore里被广泛的使用,比起之前的autofac, ...
- 使用 Microsoft.Extensions.DependencyInjection 进行依赖注入
没有 Autofac DryIoc Grace LightInject Lamar Stashbox Unity Ninject 的日子,才是好日子~~~~~~~~~~ Using .NET Core ...
- MvvmLight + Microsoft.Extensions.DependencyInjection + WpfApp(.NetCore3.1)
git clone MvvmLight失败,破网络, 就没有直接修改源码的方式来使用了 Nuget安装MvvmLightLibsStd10 使用GalaSoft.MvvmLight.Command命名 ...
- Microsoft.Extensions.DependencyInjection中的Transient依赖注入关系,使用不当会造成内存泄漏
Microsoft.Extensions.DependencyInjection中(下面简称DI)的Transient依赖注入关系,表示每次DI获取一个全新的注入对象.但是使用Transient依赖注 ...
随机推荐
- C#数组1
using System; namespace ConsoleApp3 { class Program { static void Main(string[] args) { , , , , , }; ...
- SpringMVC详解------参数绑定
SpringMVC详解------参数绑定 转载于:https://blog.csdn.net/swebin/article/details/92795422 目录 1.SpringMVC 参数绑定 ...
- The method newInstance() from the type Class is deprecated since version 9
newInstance()在 java9中已被弃用 JAVA9之前用法 Class.forName("类的全限定名").newInstance(); JAVA9之后用法 Class ...
- java.lang.NoClassDefFoundError: Could not initialize class sun.awt.X11GraphicsEnvironment
请求验证码时后台报错:java.lang.NoClassDefFoundError: Could not initialize class sun.awt.X11GraphicsEnvironment ...
- 大数据分析的下一代架构--IOTA架构设计实践[下]
大数据分析的下一代架构--IOTA架构设计实践[下] 原创置顶 代立冬 发布于2018-12-31 20:59:53 阅读数 2151 收藏 展开 IOTA架构提出背景 大数据3.0时代以前,Lam ...
- BayaiM__oracle切换归档模式步骤:
BayaiM__oracle切换归档模式(步骤): ------------------oracle11g设置归档模式和非归档模式--------------------------[root@tes ...
- 深度自编码器(Deep Autoencoder)MATLAB解读
深度自编码器(Deep Autoencoder)MATLAB解读 作者:凯鲁嘎吉 - 博客园 http://www.cnblogs.com/kailugaji/ 这篇文章主要讲解Hinton在2006 ...
- 浅谈JS递归
简单理解就是函数内部不断调用自身 直接上代码: function dg(num){ ){ ; }else{ ) } } console.log(dg()) 最后输出值为21,记住加限制条件退出递归,不 ...
- 利用Git生成本机SSH Key并添加到GitHub中
本地仓库和github之间是通过SSH加密传输的,所以需要先到github中添加你本机的SSH Key 进行认证. 1.在桌面打开git命令窗口 2.输入“ssh-keygen -t rsa -C ...
- 单臂路由和VLAN-IF
前几日有同学在韩老师的会员群里面提了这样一个问题: 有个问题搞半天没弄明白,我在核心交换机上划分了几个vlan,其中一个端口与防火墙相连,防火墙配置为192.168.100.1/30,核心交换机上连接 ...