1. 程式人生 > >解析 Microsoft.Extensions.DependencyInjection 2.x 版本實現

解析 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.DynamicServiceCollectionContainerBuilderExtensions.BuildServiceProvider()作為入口沒有控制能力,使得成員_engine是型別為DynamicServiceProviderEngine的例項。

最終實現類DynamicServiceProviderEngine

CompiledServiceProviderEngine繼承,後者再從抽象類ServiceProviderEngine繼承。

抽象類ServiceProviderEngine定義了方法GetService(Type serviceType),並維護了預設可見性的執行緒安全的字典internal ConcurrentDictionary<Type, Func<ServiceProviderEngineScope, object>> RealizedServices,目標型別例項化總是先從該字典獲取委託。

sequenceDiagram 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

這裡解析兩個重要依賴CallSiteFactoryCallSiteRuntimeResolver,以及資料結構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()為例簡單說明:

  1. 對於使用單例和常量的注入方式,返回ConstantCallSite例項;
  2. 對於使用委託的注入方式,返回FactoryCallSite例項;
  3. 對於使用型別注入的,CallSiteFactory呼叫方法CreateConstructorCallSite()
    • 如果只有1個建構函式
      • 無參建構函式,使用 CreateInstanceCallSite作為例項化上下文;
      • 有參建構函式存,首先使用方法CreateArgumentCallSites()遍歷所有引數,遞迴建立各個引數的 IServiceCallSite 例項,得到陣列。接著使用前一步得到的陣列作為引數, 創建出 ConstructorCallSite例項。
    • 如果多於1個建構函式,檢查和選取最佳建構函式再使用前一步邏輯處理;
  4. 最後新增生命週期標識

泛型、集合處理多了部分前置工作,在此略過。

如下流程圖簡要地展示了遞迴過程:

CallSiteRuntimeResolver

CallSiteRuntimeResolverCallSiteVisitor<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返回的。

sequenceDiagram 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

ExpressionResolverBuilderCallSiteVisitor<CallSiteExpressionBuilderContext, Expression>繼承,正如其名是表示式樹的相關實現,其方法Build()構建和返回型別為Func<ServiceProviderEngineScope, object>的委託。

ExpressionResolverBuilderCallSiteRuntimeResolver一樣繼承了抽象類CallSiteVisitor<TArgument, TResult>,所以解析出表示式樹的過程極其相似,根據 IServiceCallSite創建出表示式樹。

  • ConstantCallSite:使用Expression.Constant()
  • FactoryCallSite:使用Expression.Invocation()
  • CreateInstanceCallSite:使用 Expression.New()
  • ConstructorCallSite:遞迴建立各個引數的表示式樹得到陣列,接著作為引數,使用Expression.New() 建立最終的表示式樹;

ServiceProvider

回顧整個流程可知,CallSiteFactoryCallSiteRuntimeResolverExpressionResolverBuilder是目標服務例項化的核心實現:

  • CallSiteFactory:解析和快取目標服務的例項化上下文;
  • CallSiteRuntimeResolver:使用反射完成目標服務的例項化;
  • ExpressionResolverBuilder:使用表示式樹得到目標服務的例項化的前置委託;

ServiceProvider通過特殊的委託完成了目標服務例項化方式的替換:

  • 初次呼叫GetService()
    • 首先通過DynamicServiceProviderEngine返回了委託,該委託被儲存到字典 RealizedServices中;
    • 接著該委託被第1次執行,通過CallSiteRuntimeResolver完成目標服務的例項化;
  • 再將呼叫GetService()時,
    • 直接得到快取的委託並同樣完成目標服務的例項
    • 同時通過一個額外的 Task,通過ExpressionResolverBuilder 使用表示式樹重新生成委託,並操作字典RealizedServices,替換初次呼叫生成委託;
  • 後續呼叫GetService()時,字典RealizedServices查詢到的是已經替換過的使用表示式樹生成的委託。

沒有執行緒安全問題,委託一定會被替換,視表示式樹的構建完成時機。

sequenceDiagram 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/leoni