細聊.Net Core中IServiceScope的工作方式

前言

????自從.Net Core引入IOC相關的體系之后,關于它的討論就從來沒有停止過,因為它是.Net Core體系的底層框架,你只要使用了.Net Core的時候就必然會用到它。當然關于使用它的過程中產生的問題也從來沒停止過。我對待問題的態度向來都是,如果你踩到了坑,說明你還沒有足夠了解它,所以我們對它認知的突破,很多時候是遇到了問題并解決了問題。今天的話題呢,也是一個群友在研究.Net Core作用域的過程中產生的疑問,博主呢對這個問題也很有興趣,就借此機會探究了一下,把自己研究結果分享給大家。

簡單演示

在日常的開發中使用CreateScope()CreateAsyncScope()的場景可能沒有那么多,但是在ASP.NET Core底層的話這是核心設計,在上篇文章<解決ASP.NET Core在Task中使用IServiceProvider的問題>[1]中提到過,ASP.NET Core會為每次請求創建一個Scope,也就是咱們這次提到的作用域。使用的方法有很簡單,本質就是IServiceProvider的一個擴展方法。咱們今天主要說的就是ServiceLifetime.Scoped這個比較特殊的生命周期,在Scope內是如何工作的,原始點的寫法其實就是

IServiceCollection?services?=?new?ServiceCollection();
services.AddScoped<Person>(provider?=>?new()?{?Id?=?1,?Name?=?"yi念之間",?Sex?=?"Man"?});IServiceProvider?serviceProvider?=?services.BuildServiceProvider();
using?(IServiceScope?serviceScope?=?serviceProvider.CreateScope())
{var?personOne?=?serviceScope.ServiceProvider.GetService<Person>();Console.WriteLine(person.Name);
}

如果在ASP.NET Core框架里那玩法就多了,只要有IServiceProvide的地方都可以使用CreateScope()CreateAsyncScope()方法,簡單演示一下,但是如果感覺自己把握不住的話還是提前自己試驗一下

[HttpGet]
public?async?Task<object>?JudgeScope([FromServices]IServiceProvider?scopeProvider)
{using?IServiceScope?scope?=?HttpContext.RequestServices.CreateScope();Person?person?=?scope.ServiceProvider.GetService<Person>();await?using?(AsyncServiceScope?scope2?=?scopeProvider.CreateAsyncScope()){Person?person2?=?scope2.ServiceProvider.GetService<Person>();}return?person;
}

源碼探究

通過上面的示例,我們可以看到其實關于IServiceScope的操作部分就是三個核心。

  • ??通過CreateScope()CreateAsyncScope()方法創建服務作用域。

  • ??使用GetService相關的方法創建需要的對象實例。

  • ??用完了作用域之后通過使用Dispose()或者DisposeAsync()方法(using的方式同理)釋放作用域。

先說AsyncServiceScope

為了怕大家心里有疑慮,因為使用CreateScope()方法創建出來的是IServiceScope實例,使用CreateAsyncScope方法創建的是AsyncServiceScope實例。咱們這里先來說一下AsyncServiceScopeIServiceScope的關系,看了之后大家就不用惦記它了,先來看一下CreateAsyncScope()方法的定義[點擊查看源碼👈[2]]

public?static?AsyncServiceScope?CreateAsyncScope(this?IServiceProvider?provider)
{return?new?AsyncServiceScope(provider.CreateScope());
}

方法就是返回AsyncServiceScope實例,接下來來看一下這個類的定義[點擊查看源碼👈[3]]

public?readonly?struct?AsyncServiceScope?:?IServiceScope,?IAsyncDisposable
{private?readonly?IServiceScope?_serviceScope;public?AsyncServiceScope(IServiceScope?serviceScope){//AsyncServiceScope也是IServiceScope實例構建起來的_serviceScope?=?serviceScope????throw?new?ArgumentNullException(nameof(serviceScope));}//ServiceProvider也是直接在IServiceScope實例中直接獲取的public?IServiceProvider?ServiceProvider?=>?_serviceScope.ServiceProvider;//同步釋放public?void?Dispose(){_serviceScope.Dispose();}//異步釋放public?ValueTask?DisposeAsync(){//因為IAsyncDisposable的ServiceProvider能繼續創建作用域//使用CreateScope或CreateAsyncScope方法if?(_serviceScope?is?IAsyncDisposable?ad){return?ad.DisposeAsync();}_serviceScope.Dispose();return?default;}
}

通過源碼我們可以看到AsyncServiceScope本身是包裝了IServiceScope實例,它本身也是實現了IServiceScope接口并且同時IAsyncDisposable接口以便可以異步調用釋放。相信大家都知道,實現了IDispose接口可以使用using IServiceScope scope = HttpContext.RequestServices.CreateScope()的方式,它編譯完之后其實是

IServiceScope?scope?=?HttpContext.RequestServices.CreateScope();
try
{//具體操作
}
finally
{scope.Dispose();
}

實現了IAsyncDisposable接口可以使用await using (AsyncServiceScope scope2 = scopeProvider.CreateAsyncScope())的方式,它編譯完的代碼則是

AsyncServiceScope?scope2?=?scopeProvider.CreateAsyncScope();
try
{//具體操作
}
finally
{await?scope2.DisposeAsync();
}

打消了這個疑慮,相信大家對它們的關系有了了解,本質就是包裝了一下IServiceScope實例。

由創建開始

接下來我們可以專心的看一下IServiceScope相關的實現,IServiceScope的創建則是來自IServiceProvider的擴展方法CreateScope(),首先看下它的定義[點擊查看源碼👈[4]]

public?static?IServiceScope?CreateScope(this?IServiceProvider?provider)
{return?provider.GetRequiredService<IServiceScopeFactory>().CreateScope();
}

好吧,短短的一行代碼,我們可以得到兩個比較重要的信息

  • ??首先獲取到的IServiceScopeFactory實例,看過上篇文章的可以知道,默認情況通過IServiceScopeFactory實例獲取的是根容器的實例。

  • ??其次IServiceProvider的CreateScope擴展方法,本質是調用的IServiceScopeFactoryCreateScope方法。

接下來我們就看看下IServiceScopeFactory默認實現類中關于CreateScope()方法的定義,在ServiceProviderEngineScope類中[點擊查看源碼👈[5]]

internal?ServiceProvider?RootProvider?{?get;?}
public?IServiceScope?CreateScope()?=>?RootProvider.CreateScope();

這里毫無疑問了RootProvider屬性里的實例都是來自根容器,而CreateScope()方法則是調用的ServiceProviderCreateScope()方法。看下ServiceProvider類的CreateScope方法定義 [點擊查看源碼👈[6]]

private?bool?_disposed;
internal?IServiceScope?CreateScope()
{//判斷當前ServiceProvider是否被釋放if?(_disposed){//如果已經釋放則直接拋出異常ThrowHelper.ThrowObjectDisposedException();}//創建ServiceProviderEngineScope實例return?new?ServiceProviderEngineScope(this,?isRootScope:?false);
}

通過上面的代碼我們可以看到CreateScope()方法,本質是創建了一個ServiceProviderEngineScope方法實例。通過創建的這一行代碼,好巧不巧又可以得到兩個重要的信息。

  • ??一是ServiceProviderEngineScope構造函數的第一個參數,傳遞的是當前的ServiceProvider實例。

  • ??二是ServiceProviderEngineScope構造函數的第二個參數叫isRootScope值給的是false,說明當前ServiceProviderEngineScope實例不是根作用域,也就是我們說的子作用域。

大致看一下ServiceProviderEngineScope構造函數的實現[點擊查看源碼👈[7]]

internal?sealed?class?ServiceProviderEngineScope?:?IServiceScope,?IServiceProvider,?IAsyncDisposable,?IServiceScopeFactory
{internal?Dictionary<ServiceCacheKey,?object>?ResolvedServices?{?get;?}internal?object?Sync?=>?ResolvedServices;internal?ServiceProvider?RootProvider?{?get;?}public?bool?IsRootScope?{?get;?}//IServiceProvider的ServiceProvider屬性則是賦值的當前實例public?IServiceProvider?ServiceProvider?=>?this;public?ServiceProviderEngineScope(ServiceProvider?provider,?bool?isRootScope){//用來存儲當前作用域管理的對象實例ResolvedServices?=?new?Dictionary<ServiceCacheKey,?object>();//創建當前實例的根容器RootProvider?=?provider;//標識當前作用域是否是根容器IsRootScope?=?isRootScope;}
}

下大致看一下,因為接下來會涉及到ServiceProviderEngineScope這個類。到目前為止涉及到了兩個比較重要的類ServiceProviderServiceProviderEngineScope,它們都是實現了IServiceProvider接口。看起來有點亂的樣子,不過我們可以姑且這么理解。ServiceProvider是IServiceProvider的直系實現類,作為IServiceProvider根容器的實現。ServiceProviderEngineScope是用于,通過IServiceProvider創建作用域時表現出來的實例,也就是非根容器的實例。

探究獲取方法

關于上面的介紹,我們其實探究了一點serviceProvider.CreateScope(),接下來我們就需要看一下關于獲取的相關操作,也就是GetService方法相關,它的使用形式是serviceScope.ServiceProvider.GetService<T>()。上面我們提到過ServiceProviderEngineScopeServiceProvider屬性實例則是當前ServiceProviderEngineScope的實例,所以我們直接去看ServiceProviderEngineScopeGetService方法[點擊查看源碼👈[8]]

internal?sealed?class?ServiceProviderEngineScope?:?IServiceScope,?IServiceProvider,?IAsyncDisposable,?IServiceScopeFactory
{private?bool?_disposed;internal?ServiceProvider?RootProvider?{?get;?}public?object?GetService(Type?serviceType){//判斷當前實例是否釋放if?(_disposed){//如果已經釋放則直接拋出異常ThrowHelper.ThrowObjectDisposedException();}return?RootProvider.GetService(serviceType,?this);}
}

看著挺亂的,各種跳轉各種調用。不過本質只設計到兩個類ServiceProviderServiceProviderEngineScope,先說明一下,省的大家看著整蒙圈了。通過最后一句代碼,我們又能得到兩個比較重要的信息。

  • ? ServiceProviderEngineScope的GetService方法,本質是在調用RootProvider的GetService方法。通過前面咱們的源碼分析可以知道RootProvider屬性的值是ServiceProvider實例也就是代表的根容器。

  • ??調用RootProvider的GetService方法的時候傳遞了當前ServiceProviderEngineScope實例。

接下來就可以直接找到ServiceProvider的GetService方法了,看一下里面的具體相關實現[點擊查看源碼👈[9]]

public?sealed?class?ServiceProvider?:?IServiceProvider,?IDisposable,?IAsyncDisposable
{private?bool?_disposed;private?ConcurrentDictionary<Type,?Func<ServiceProviderEngineScope,?object>>?_realizedServices;private?readonly?Func<Type,?Func<ServiceProviderEngineScope,?object>>?_createServiceAccessor;internal?ServiceProviderEngine?_engine;internal?ServiceProvider(){_createServiceAccessor?=?CreateServiceAccessor;_realizedServices?=?new?ConcurrentDictionary<Type,?Func<ServiceProviderEngineScope,?object>>();}internal?object?GetService(Type?serviceType,?ServiceProviderEngineScope?serviceProviderEngineScope){//判斷當前實例是否釋放if?(_disposed){ThrowHelper.ThrowObjectDisposedException();}//緩存獲取服務實例委托的字典,值為要獲取實例的類型,值是創建實例的委托//_createServiceAccessor本質是CreateServiceAccessor方法委托Func<ServiceProviderEngineScope,?object>?realizedService?=?_realizedServices.GetOrAdd(serviceType,?_createServiceAccessor);OnResolve(serviceType,?serviceProviderEngineScope);DependencyInjectionEventSource.Log.ServiceResolved(this,?serviceType);//執行realizedService委托,傳遞的是ServiceProviderEngineScope實例var?result?=?realizedService.Invoke(serviceProviderEngineScope);System.Diagnostics.Debug.Assert(result?is?null?||?CallSiteFactory.IsService(serviceType));return?result;}private?Func<ServiceProviderEngineScope,?object>?CreateServiceAccessor(Type?serviceType){//獲取ServiceCallSite,其實就是獲取要解析對象的實例相關信息ServiceCallSite?callSite?=?CallSiteFactory.GetCallSite(serviceType,?new?CallSiteChain());if?(callSite?!=?null){DependencyInjectionEventSource.Log.CallSiteBuilt(this,?serviceType,?callSite);OnCreate(callSite);//咱們當前討論的是Scope周期對象的問題,這個方法描述的是生命周期為ServiceLifetime.Singleton的情況,可以跳過這個邏輯//如果是單例情況,則直接在根容器中直接去操作對象實例,和當前的ServiceProviderEngineScope無關if?(callSite.Cache.Location?==?CallSiteResultCacheLocation.Root){object?value?=?CallSiteRuntimeResolver.Instance.Resolve(callSite,?Root);return?scope?=>?value;}//解析ServiceCallSite里的信息return?_engine.RealizeService(callSite);}return?_?=>?null;}
}

這里我們看下CallSiteFactory.GetCallSite方法,先來說一下這個方法是做啥的。我們要獲取某個類型的實例(可以理解為我們演示示例里的Person類),但是我們注冊類相關的信息的時候(比如上面的services.AddScoped<Person>(provider => new() { Id = 1, Name = "yi念之間", Sex = "Man" }))涉及到幾種方式,比如AddScoped<T>Add<T>(Func<IServiceProvider,object>),我們需要知道創建類型實例的時候使用哪種方式(比如我們的Person是使用委托的這種方式),ServiceCallSite正是存儲的類型和如何創建這個類型的工廠相關的信息。我們來看一下GetCallSite方法的核心操作[點擊查看源碼👈[10]]

private?readonly?ConcurrentDictionary<ServiceCacheKey,?ServiceCallSite>?_callSiteCache?=?new?ConcurrentDictionary<ServiceCacheKey,?ServiceCallSite>();private?ServiceCallSite?TryCreateExact(ServiceDescriptor?descriptor,?Type?serviceType,?CallSiteChain?callSiteChain,?int?slot)
{if?(serviceType?==?descriptor.ServiceType){//要獲取的類型會被包裝成ServiceCacheKeyServiceCacheKey?callSiteKey?=?new?ServiceCacheKey(serviceType,?slot);//在緩存中獲取ServiceCallSite實例,可以理解為設計模式中的享元模式if?(_callSiteCache.TryGetValue(callSiteKey,?out?ServiceCallSite?serviceCallSite)){return?serviceCallSite;}ServiceCallSite?callSite;//根據ServiceDescriptor.Lifetime包裝ResultCachevar?lifetime?=?new?ResultCache(descriptor.Lifetime,?serviceType,?slot);//ServiceDescriptor就是我們添加到IServiceCollection的最終形式//我們注冊服務的時候本質就是在IServiceCollection里添加ServiceDescriptor實例//AddScope<T>()這種形式if?(descriptor.ImplementationInstance?!=?null){callSite?=?new?ConstantCallSite(descriptor.ServiceType,?descriptor.ImplementationInstance);}//AddScope(Func<IServiceProvider,object>)形式else?if?(descriptor.ImplementationFactory?!=?null){callSite?=?new?FactoryCallSite(lifetime,?descriptor.ServiceType,?descriptor.ImplementationFactory);}//AddScope<T,TImpl>()形式else?if?(descriptor.ImplementationType?!=?null){callSite?=?CreateConstructorCallSite(lifetime,?descriptor.ServiceType,?descriptor.ImplementationType,?callSiteChain);}else{throw?new?InvalidOperationException(SR.InvalidServiceDescriptor);}//將創建的ServiceCallSite緩存起來return?_callSiteCache[callSiteKey]?=?callSite;}return?null;
}

而解析ServiceCallSite實例的方法RealizeService(ServiceCallSite)則是在ServiceProviderEngine類中,看一下相關實現[點擊查看源碼👈[11]]

public?override?Func<ServiceProviderEngineScope,?object>?RealizeService(ServiceCallSite?callSite)
{int?callCount?=?0;return?scope?=>{//核心代碼是Resolve方法,這里的scope則是ServiceProviderEngineScope//即我們上面通過CreateScope()創建的實例var?result?=?CallSiteRuntimeResolver.Instance.Resolve(callSite,?scope);if?(Interlocked.Increment(ref?callCount)?==?2){_?=?ThreadPool.UnsafeQueueUserWorkItem(_?=>{try{_serviceProvider.ReplaceServiceAccessor(callSite,?base.RealizeService(callSite));}catch?(Exception?ex){//省略掉非核心代碼}},null);}return?result;};
}

上面我們看到的RealizeService()方法返回的是一個委托,而調用這個委托的地方則是上面源碼中看到的realizedService.Invoke(serviceProviderEngineScope),核心操作在CallSiteRuntimeResolver.Instance.Resolve()方法,Resolve方法的核心邏輯在VisitCallSite()方法,看一下它的實現方式[點擊查看源碼👈[12]]

protected?virtual?TResult?VisitCallSite(ServiceCallSite?callSite,?TArgument?argument)
{if?(!_stackGuard.TryEnterOnCurrentStack()){return?_stackGuard.RunOnEmptyStack((c,?a)?=>?VisitCallSite(c,?a),?callSite,?argument);}switch?(callSite.Cache.Location){//ServiceLifetime.Singleton單例情況case?CallSiteResultCacheLocation.Root:return?VisitRootCache(callSite,?argument);//ServiceLifetime.Scoped作用域情況,也就是咱們關注的情況case?CallSiteResultCacheLocation.Scope:return?VisitScopeCache(callSite,?argument);//ServiceLifetime.Transient瞬時情況case?CallSiteResultCacheLocation.Dispose:return?VisitDisposeCache(callSite,?argument);case?CallSiteResultCacheLocation.None:return?VisitNoCache(callSite,?argument);default:throw?new?ArgumentOutOfRangeException();}
}

因為我們關注的是CallSiteResultCacheLocation.Scope這種情況所以我們重點關注的是VisitScopeCache()這段方法邏輯,CallSiteRuntimeResolver的VisitCache()方法[點擊查看源碼👈[13]]

protected?override?object?VisitScopeCache(ServiceCallSite?callSite,?RuntimeResolverContext?context)
{//咱們關注的是Scope的情況,所以重點在VisitCache方法return?context.Scope.IsRootScope??VisitRootCache(callSite,?context)?:VisitCache(callSite,?context,?context.Scope,?RuntimeResolverLock.Scope);
}//這里的serviceProviderEngine參數就是我們傳遞進來的ServiceProviderEngineScope實例
private?object?VisitCache(ServiceCallSite?callSite,?RuntimeResolverContext?context,?ServiceProviderEngineScope?serviceProviderEngine,?RuntimeResolverLock?lockType)
{bool?lockTaken?=?false;//獲取ServiceProviderEngineScope的Sync屬性object?sync?=?serviceProviderEngine.Sync;//獲取ServiceProviderEngineScope的ResolvedServices屬性Dictionary<ServiceCacheKey,?object>?resolvedServices?=?serviceProviderEngine.ResolvedServices;//加鎖if?((context.AcquiredLocks?&?lockType)?==?0){Monitor.Enter(sync,?ref?lockTaken);}try{//判斷ServiceProviderEngineScope的ResolvedServices的屬性里是否包含該類型實例//當前作用域內只有一個實例,所以緩存起來if?(resolvedServices.TryGetValue(callSite.Cache.Key,?out?object?resolved)){return?resolved;}//當前Scope沒創建過實例的話則創建resolved?=?VisitCallSiteMain(callSite,?new?RuntimeResolverContext{Scope?=?serviceProviderEngine,AcquiredLocks?=?context.AcquiredLocks?|?lockType});//判斷當前類型實例是否是IDispose想實例serviceProviderEngine.CaptureDisposable(resolved);//給ServiceProviderEngineScope的ResolvedServices的屬性添加緩存實例resolvedServices.Add(callSite.Cache.Key,?resolved);return?resolved;}finally{if?(lockTaken){Monitor.Exit(sync);}}
}protected?virtual?TResult?VisitCallSiteMain(ServiceCallSite?callSite,?TArgument?argument)
{//比如我們上面的services.AddScoped<Person>(provider?=>?new()?{?Id?=?1,?Name?=?"yi念之間",?Sex?=?"Man"?})//對應的Kind則是CallSiteKind.Factoryswitch?(callSite.Kind){case?CallSiteKind.Factory://調用了VisitFactory方法return?VisitFactory((FactoryCallSite)callSite,?argument);case??CallSiteKind.IEnumerable:return?VisitIEnumerable((IEnumerableCallSite)callSite,?argument);case?CallSiteKind.Constructor:return?VisitConstructor((ConstructorCallSite)callSite,?argument);case?CallSiteKind.Constant:return?VisitConstant((ConstantCallSite)callSite,?argument);case?CallSiteKind.ServiceProvider:return?VisitServiceProvider((ServiceProviderCallSite)callSite,?argument);default:throw?new?NotSupportedException(SR.Format(SR.CallSiteTypeNotSupported,?callSite.GetType()));}
}protected?override?object?VisitFactory(FactoryCallSite?factoryCallSite,?RuntimeResolverContext?context)
{//調用我們注冊的services.AddScoped<Person>(provider?=>?new()?{?Id?=?1,?Name?=?"yi念之間",?Sex?=?"Man"?})//FactoryCallSite的Factory即是provider?=>?new()?{?Id?=?1,?Name?=?"yi念之間",?Sex?=?"Man"?}//context.Scope則是我們通過CreateScope創建的實例//返回的結果就是調用當前委托得到的實例即我們實例中的Person實例return?factoryCallSite.Factory(context.Scope);
}

回過頭來說一下咱們上面展示的代碼,被調用執行的地方就是GetService方法里的realizedService.Invoke(serviceProviderEngineScope)的這段代碼。上面的執行邏輯里涉及到了ServiceProviderEngineScope里的幾個操作,比如ResolvedServices屬性和CaptureDisposable()方法,看一下相關的代碼

internal?sealed?class?ServiceProviderEngineScope?:?IServiceScope,?IServiceProvider,?IAsyncDisposable,?IServiceScopeFactory
{internal?IList<object>?Disposables?=>?_disposables????(IList<object>)Array.Empty<object>();private?bool?_disposed;private?List<object>?_disposables;internal?Dictionary<ServiceCacheKey,?object>?ResolvedServices?{?get;?}public?ServiceProviderEngineScope(ServiceProvider?provider,?bool?isRootScope){ResolvedServices?=?new?Dictionary<ServiceCacheKey,?object>();}internal?object?CaptureDisposable(object?service){//判斷實例是否實現了IDisposable或IAsyncDisposable接口,因為這種需要在當前作用域是否的時候一起釋放if?(ReferenceEquals(this,?service)?||?!(service?is?IDisposable?||?service?is?IAsyncDisposable)){return?service;}bool?disposed?=?false;lock?(Sync){//判斷當前作用域是否釋放if?(_disposed){disposed?=?true;}else{???//如果滿足則添加到_disposables待釋放集合,以便作用域釋放的時候一起釋放_disposables???=?new?List<object>();_disposables.Add(service);}}//如果當前作用域已經被釋放則直接釋放當前實例if?(disposed){//前提是服務實例是實現IDisposable或IAsyncDisposable接口的if?(service?is?IDisposable?disposable){disposable.Dispose();}else{Task.Run(()?=>?((IAsyncDisposable)service).DisposeAsync().AsTask()).GetAwaiter().GetResult();}ThrowHelper.ThrowObjectDisposedException();}return?service;}
}

上面關于ServiceProviderEngineScope類中涉及到GetService()方法的相關邏輯已經展示的差不多了,涉及到的比較多,而且看著比較亂。不過如果理解了思路還是比較清晰的,咱們來做一下一個大概的總結。

  • ??首先,需要獲取ServiceCallSite,在方法GetCallSite()中,其實就是獲取要解析對象的實例相關信息。我們需要知道創建類型實例的時候使用哪種方式(比如我們的Person是使用委托的這種方式),其中也包括該對象創建的類型、創建工廠、生命周期類型。

  • ??然后,得到ServiceCallSite實例之后,我們就可以通過實例創建的信息去創建信息,在方法RealizeService()里。根據不同類型創建方式和生命周期,判斷如何創建對象,即對象存放位置。

  • ??最后,如果是單例模式,則在根容器中解析這個對象,位置當然也是存儲在根容器中,全局唯一。如果是瞬時模式,則直接返回創建的對象實例,不進行任何存儲,但是需要判斷實例是否實現了IDisposable或IAsyncDisposable接口,如果是則加入當前ServiceProviderEngineScope實例的_disposables集合。如果是Scope模式就比較特殊了,因為Scope需要在當前ServiceProviderEngineScope中存儲保證當前作用域唯一,則需要添加到ResolvedServices屬性的字典里,同時也需要判斷是否需要添加到_disposables集合里。

這就可以解釋ServiceProviderEngineScope針對不同生命周期的存儲方式了,單例的情況創建和存儲都是在根容器中,瞬時的情況下則每次都創建新的實例且不進行存儲,Scope的情況下則是存儲在當前的ResolvedServices中享元起來可以在當前作用域內重復使用。

關于結束釋放

前面咱們看了下關于作用域創建,在做用戶獲取對象的相關邏輯。接下來我們來看一下三件套的最后一個步驟,釋放邏輯相關的。這個邏輯比較簡單,上面咱們或多或少的也說過了一點,釋放分為同步釋放和異步釋放兩種情況,咱們看一下同步釋放的相關實現[點擊查看源碼👈[14]]

internal?Dictionary<ServiceCacheKey,?object>?ResolvedServices?{?get;?}
internal?object?Sync?=>?ResolvedServices;
private?bool?_disposed;
private?List<object>?_disposables;
public?void?Dispose()
{List<object>?toDispose?=?BeginDispose();if?(toDispose?!=?null){for?(int?i?=?toDispose.Count?-?1;?i?>=?0;?i--){???//模仿棧模式,最后創建的最先釋放if?(toDispose[i]?is?IDisposable?disposable){//釋放的正式實現了IDisposable接口的對象disposable.Dispose();}else{throw?new?InvalidOperationException(SR.Format(SR.AsyncDisposableServiceDispose,?TypeNameHelper.GetTypeDisplayName(toDispose[i])));}}}
}private?List<object>?BeginDispose()
{//本質就是鎖住當前存儲對象的集合,不允許進行任何操作lock?(Sync){//如果已經釋放過了則直接返回if?(_disposed){return?null;}DependencyInjectionEventSource.Log.ScopeDisposed(RootProvider.GetHashCode(),?ResolvedServices.Count,?_disposables?.Count????0);//先把釋放標識設置了_disposed?=?true;}//判斷是否是根容器釋放if?(IsRootScope?&&?!RootProvider.IsDisposed()){RootProvider.Dispose();}return?_disposables;
}

其實主要邏輯就是循環釋放_disposables里的所有對象,也就是實現了IDisposable接口的對象。接下來咱們再來看一下異步釋放的相關邏輯。

public?ValueTask?DisposeAsync()
{List<object>?toDispose?=?BeginDispose();if?(toDispose?!=?null){try{for?(int?i?=?toDispose.Count?-?1;?i?>=?0;?i--){object?disposable?=?toDispose[i];//判斷是否是實現了IAsyncDisposable接口的對象if?(disposable?is?IAsyncDisposable?asyncDisposable){//獲取DisposeAsync方法返回值也就是ValueTaskValueTask?vt?=?asyncDisposable.DisposeAsync();if?(!vt.IsCompletedSuccessfully){return?Await(i,?vt,?toDispose);}//阻塞等待DisposeAsync執行完成vt.GetAwaiter().GetResult();}else{((IDisposable)disposable).Dispose();}}}catch?(Exception?ex){return?new?ValueTask(Task.FromException(ex));}}return?default;static?async?ValueTask?Await(int?i,?ValueTask?vt,?List<object>?toDispose){//等待DisposeAsync方法里的邏輯執行完成await?vt.ConfigureAwait(false);i--;for?(;?i?>=?0;?i--){object?disposable?=?toDispose[i];if?(disposable?is?IAsyncDisposable?asyncDisposable){//等待DisposeAsync執行完成await?asyncDisposable.DisposeAsync().ConfigureAwait(false);}else{((IDisposable)disposable).Dispose();}}}
}

其實核心邏輯和同步釋放是一致的,只是IAsyncDisposable接口中的DisposeAsync()方法返回的異步相關的ValueTask所以需要進行一些等待相關的操作。不過本質都是循環釋放_disposables里的數據,而這些數據正是當前作用域里里實現了IDisposable或IAsyncDisposable接口的實例。

使用CreateScope()GetService()方法的時候,都會判斷當前作用域是否釋放,而這個標識正是在Dispose()DisposeAsync()置為true的。我們上面文章中的那個異常的引發點也正是這里,也正是因為作用域被釋放了表示為置為true了,所以GetService才會直接拋出異常。

群友問題解答

關于ServiceProviderEngineScope重要的相關實現,咱們上面已經大致的講解過了。其實探索它的原動力就是因為群友遇到的一些關于這方面的疑問,如果了解了它的實現的話便能輕松的解除心中的疑問,還原一下當時有疑問的代碼

IServiceCollection?services?=?new?ServiceCollection();
services.AddScoped<Person>(provider?=>?new()?{?Id?=?1,?Name?=?"yi念之間",?Sex?=?"Man"?});IServiceProvider?serviceProvider?=?services.BuildServiceProvider();
Person?person?=?null;
using?(IServiceScope?serviceScope?=?serviceProvider.CreateScope())
{person?=?serviceScope.ServiceProvider.GetService<Person>();Console.WriteLine(person.Name);
}
if?(person?==?null)
{Console.WriteLine("Person被回收了");
}

代碼大致描述的就是當時的這么一個場景,這里毫無疑問哈,完全判斷不出來Person是否已經被回收。通過上面的源碼咱們就可以知道,無論是掉用ServiceProviderEngineScope的是Dispose()DisposeAsync()方法(using上面咱們說過了就是語法糖),其實都是調用了當前作用域內實現了IDisposableIAsyncDisposable接口的實例里的Dispose()DisposeAsync()方法。

  • ??即使當前實例實現了IDisposableIAsyncDisposable接口,且調用了實例內的Dispose()DisposeAsync()方法,也不意味著當前對象已經被釋放了,因為我們用Dispose方法里多半是這個對象里引用的非托管代碼的釋放工作,并不意味這當前對象被釋放了。

  • ??IServiceScope實現類ServiceProviderEngineScope里ResolvedServices屬性享元的實例,也就是生命周期為ServiceLifetime.Scoped的實例。這些實例何時被回收是取決于兩點,一是當前實例的訪問是否超出當前作用域,二是當前對象是否有被引用。上面的示例中IServiceScope實例雖然已經超出作用了(因為在using括號之外了),但是Person外出的棧里還引用著ResolvedServices字典里Person對象的實例,所以GC即垃圾回收機制并不會回收這個實例,因為它還在被引用。那就意味著它不能被釋放,也就不存在Person實例被回收這么一說了。

所以,上面的問題說起來就是IServiceScope主要解決的是對象取的問題,也就是我用我的字典屬性保留了需要保留的對象實例,可以釋放被聲明可以釋放的操作(比如非托管資源的釋放)。但是作用域本身的回收和內部管理的對象的回收是交給GC來負責的。

細想一下就會明白了,托管對象的回收本身就是垃圾回收處理的,就和你自己寫單例模式或者直接new一個對象實例的時候,你也沒考慮對象的回收問題,因為垃圾回收機制已經幫你處理了。

總結

????在.Net Core體系中IOC一直是核心模塊,且關于Scope的作用域的問題,一直會有人產生疑問,想更深刻的了解一下還是得多拿一些時間研究一下。有些知識不是靠一時半會的學就能牢牢地掌握,需要日常不斷的積累和不斷的解決問題,才能掌握的更多。因為設計到的源碼比較多,而且不熟悉的話可能不是很好理解,所以還需要平時的積累,積累的越多能解決的問題越多,才能避免入坑。好了大致總結一下

  • ??當我們使用CreateScope()CreateAsyncScope()創建出ServiceProviderEngineScopeAsyncServiceScope實例的時候,即我們通常描述的作用域。這個實例里包含了ResolvedServices屬性和Disposables屬性,分別保存當前作用域內即生命周期為ServiceLifetime.Scoped實例和實現了IDisposableIAsyncDisposable接口的實例。

  • ??使用GetService()方法在當前作用域內獲取實例的時候,會根據服務注冊時使用的生命周期判斷是否加入到當前作用域里享元的實例。其中單例來自于根容器,瞬時的每次都需要創建新的實例所以不需要保存,只有生命周期為ServiceLifetime.Scoped才保存。瞬時的和Scope的對象創建出來的時候都會判斷是否實現了IDisposableIAsyncDisposable接口,如果是則加入到Disposables屬性的集合里用于釋放。

  • ??當前作用域被釋放的時候,即調用IServiceScope實例Dispose()相關方法的時候,會遍歷Disposables集合里的對象進行Dispose相關方法調用,并不是回收托管到當前作用域內的對象,因為對象何時被回收取決于GC即垃圾回收機制。

好了到這里差不多,歡迎大家多多交流。寒冬已至,希望大家都有御寒的方法。分享一下看到過的一句話。你能得到的最牢靠的一定得是依靠你自身實力建立起來的,而不是你所處的平臺建立起來的,因為依賴平臺建立起來的,離開這個平臺會打折。

引用鏈接

[1]?<解決ASP.NET Core在Task中使用IServiceProvider的問題>:?https://www.cnblogs.com/wucy/p/16566495.html
[2]?點擊查看源碼👈:?https://github.com/dotnet/runtime/blob/v6.0.10/src/libraries/Microsoft.Extensions.DependencyInjection.Abstractions/src/ServiceProviderServiceExtensions.cs#L134
[3]?點擊查看源碼👈:?https://github.com/dotnet/runtime/blob/v6.0.10/src/libraries/Microsoft.Extensions.DependencyInjection.Abstractions/src/AsyncServiceScope.cs
[4]?點擊查看源碼👈:?https://github.com/dotnet/runtime/blob/v6.0.10/src/libraries/Microsoft.Extensions.DependencyInjection.Abstractions/src/ServiceProviderServiceExtensions.cs#L124
[5]?點擊查看源碼👈:?https://github.com/dotnet/runtime/blob/v6.0.10/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/ServiceProviderEngineScope.cs#L49
[6]?點擊查看源碼👈:?https://github.com/dotnet/runtime/blob/v6.0.10/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceProvider.cs#L182
[7]?點擊查看源碼👈:?https://github.com/dotnet/runtime/blob/v6.0.10/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/ServiceProviderEngineScope.cs#L19
[8]?點擊查看源碼👈:?https://github.com/dotnet/runtime/blob/v6.0.10/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/ServiceProviderEngineScope.cs#L37
[9]?點擊查看源碼👈:?https://github.com/dotnet/runtime/blob/v6.0.10/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceProvider.cs#L120
[10]?點擊查看源碼👈:?https://github.com/dotnet/runtime/blob/v6.0.10/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteFactory.cs#L313
[11]?點擊查看源碼👈:?https://github.com/dotnet/runtime/blob/v6.0.10/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/DynamicServiceProviderEngine.cs#L19
[12]?點擊查看源碼👈:?https://github.com/dotnet/runtime/blob/v6.0.10/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteVisitor.cs#L17
[13]?點擊查看源碼👈:?https://github.com/dotnet/runtime/blob/v6.0.10/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/CallSiteRuntimeResolver.cs#L111
[14]?點擊查看源碼👈:?https://github.com/dotnet/runtime/blob/v6.0.10/src/libraries/Microsoft.Extensions.DependencyInjection/src/ServiceLookup/ServiceProviderEngineScope.cs#L92

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/281876.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/281876.shtml
英文地址,請注明出處:http://en.pswp.cn/news/281876.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

WPF 獲取鼠標屏幕位置、窗口位置、控件位置

原文:WPF 獲取鼠標屏幕位置、窗口位置、控件位置public struct POINT{public int X;public int Y;public POINT(int x, int y){this.X x;this.Y y;}}[DllImport("user32.dll")] public static extern bool GetCursorPos(out POINT lpPoint);//e.GetPosition(this);…

java 復制excel_Java 復制Excel工作表

本文歸納了關于Java如何復制Excel工作表的方法&#xff0c;按不同復制需求&#xff0c;可分為&#xff1a;1. 復制工作表1.1 在同一個工作簿內復制工作表1.2 在不同工作簿間復制工作表2. 復制指定單元格數據對于復制方法copy()&#xff0c;這里簡單整理了一個表格&#xff0c;其…

AngularDart 現已全面采用 Dart 開發

一直以來&#xff0c;Angular 2依然采用TypeScript作為主流開發語言&#xff0c;然后自動編譯成JavaScript 和 Dart。Dart開發者一直在進行Angular 2拆分&#xff0c;將其分為TypeScript/JavaScript版本和Dart版本&#xff0c;并成立了AngularDart團隊。 昨天&#xff0c;Angul…

Blazor學習之旅(4)數據共享

【Blazor】| 總結/Edison Zhou大家好&#xff0c;我是Edison。前幾天沒有發布本篇就發布了第五篇&#xff0c;屬于操作失誤哈&#xff0c;這次把第四篇補上&#xff01;本篇&#xff0c;我們來了解下在Blazor中數據是如何共享的&#xff0c;組件之間又該如何傳遞參數。關于Blaz…

Zynq7000開發系列-5(OpenCV開發環境搭建:Ubuntu、Zynq)

操作系統&#xff1a;Ubuntu14.04.5 LTS 64bit OpenCV&#xff1a;OpenCV 3.1.0、opencv_contrib gcc&#xff1a;gcc version 4.8.4 (Ubuntu 4.8.4-2ubuntu1~14.04.3) arm-xilinx-linux-gnueabi-gcc&#xff1a;gcc version 4.9.2 (Sourcery CodeBench Lite 2015.05-17) 一、U…

# 20172307 2018-2019-1 《程序設計與數據結構》第5周學習總結

20172307 2018-2019-1 《程序設計與數據結構》第5周學習總結 教材學習內容總結 查找 1.線性查找&#xff1a;從該列表頭開始依次比較每一個值&#xff0c;直至找到該目標元素。2.二分查找法&#xff1a;二分查找是從排序列表的中間開始查找&#xff0c;如果沒有在那個中間元素則…

java 生成jni_Javah生成JNI頭文件

在eclipse中建一項目&#xff0c;建一class1 packageesmart.colfile.parse;2 3 publicclassTestHello {4 static{5 System.loadLibrary("TestHello");6 }7 8 publicstaticnativevoidhello(String msg);9 10 publicstaticvoidmain(String[] args) {11 12 hello("…

IBM 的大型機 z Systems 引入 Go 語言

據 cbronline 報道&#xff0c;IBM 正把 Go 語言運用到旗下的大型機上。 IBM 將開源的 Go 語言引入到 z Systems 大型機后&#xff0c;可以給用戶多一個的選擇&#xff0c;即在大型機上使用 Linux 或基于 Go 的應用&#xff0c;同時也使大型機更加靈活。Go 語言在高并發的網絡應…

WPF-10 邏輯樹和可視化樹

我們在WPF-03 資源之Resources結尾中介紹邏輯樹和可視化樹的基本概念&#xff0c;我們這節來介紹這兩棵樹邏輯樹&#xff08;Logical Tree&#xff09;邏輯樹是由每個控件的節點組成&#xff0c;本質上就是XAML文件中的UI元素&#xff0c;我們可以通過LogicalTreeHelper類提供的…

洛谷P4364 [九省聯考2018]IIIDX(線段樹)

傳送門 題解看得……很……迷&#xff1f; 因為取完一個數后&#xff0c;它的子樹中只能取權值小于等于它的數。我們先把權值從大到小排序&#xff0c;然后記$a_i$為他左邊&#xff08;包括自己&#xff09;所有取完他還能取的數的個數。那么當取完一個點$x$的數之后&#xff0…

國產車崛起粉碎德日工業神話

由于二戰戰敗&#xff0c;德國一大批頂尖人才被美蘇瓜分&#xff0c;戰敗國地位和人才斷層導致德國工業基本是第二次工業革命的產物&#xff0c;專精于機械、化工等傳統行業&#xff0c;并有巴斯夫、拜爾、大眾、戴姆勒、寶馬等一批世界級企業。不過&#xff0c;德國世界級的IT…

java hibernate 分頁查詢_4 Hibernate HQL查詢,分頁查詢

/*** HQL查詢的一個例子*/public static void hql(){Session s null;try{s HibernateUtil.getSeesion();//final String hql "from User as u where u.name?";final String hql "from User as u where u.name:name";final Query query s.createQuery…

Linux -sed

sed &#xff0c;查找sed -n /root/p passwd #列出passwd中有root的行 sed -nr /ot/p passwd #sed -r grep -E 都是進行脫意 sed -nr /0{2}/p passwd #匹配兩次o的 sed -nr /root|bus/p passwd #匹配root 或者bus的 sed -n 2p passwd # 查找指定的行sed -n 2,5p passwd # 查找…

h5 端圖片上傳-模擬多張上傳

1、由于后端的限制&#xff0c;上傳圖片到服務器只能的一張一張傳2、顯示圖片預覽是本地的圖片3、根據服務器返回的結果拿到相應的路徑保存到提交評論的接口中4、刪除的時候&#xff0c;需要刪除對應的路徑&#xff0c;不要把刪除的提交到評論的接口中 A、comment-detail.js va…

node安裝問題

1.最好安裝到默認路徑&#xff0c;手賤安到了D盤&#xff0c;升級npm各種出錯。 明明升級成功&#xff0c;查看版本時&#xff0c;確顯示依然是老的版本。 原因&#xff1a;升級的是C盤的node_modules中的npm&#xff0c;執行時確是D盤node自帶的npm&#xff0c;不知道為啥。。…

全新升級的AOP框架Dora.Interception[匯總,共6篇]

多年之前利用IL Emit寫了一個名為Dora.Interception的AOP框架。前幾天利用Roslyn的Source Generator對自己為公司寫的一個GraphQL框架進行改造&#xff0c;性能得到顯著的提高&#xff0c;覺得類似的機制同樣可以用在AOP框架上&#xff0c;實驗證明這樣的實現方式不僅僅極大地改…

java string轉decimal_java中string轉bigdecimal的例子

小編知道在java中數據類型非常 的嚴格了&#xff0c;我們如果一個地方不小心就會導致應用出問題了&#xff0c;今天 小編就在string 轉BigDecimal上碰到了一些問題&#xff0c;下面整理了幾個例子大家一起來看看。例子1,string 轉BigDecimalpublic class Test{public static vo…

通過url來設置log4j的記錄級別

2019獨角獸企業重金招聘Python工程師標準>>> 直接看代碼。 import org.apache.log4j.AppenderSkeleton; import org.apache.log4j.Level; import org.apache.log4j.LogManager; import org.apache.log4j.Logger; import org.springframework.beans.factory.annotati…

通過用戶模型,對數據庫進行增刪改查操作

增加&#xff1a;user db.session.add(user)db.session.commit() #增加 user User(username JACKSON,password0328 ) db.session.add(user) db.session.commit() 查詢&#xff1a;User.query.filter(User.username mis1114).first() #查詢 userUser.query.filter(User.usern…

Android OpenGL ES(七)----理解紋理與紋理過濾

1.理解紋理 OpenGL中的紋理能夠用來表示圖像。照片&#xff0c;甚至由一個數學算法生成的分形數據。每一個二維的紋理都由很多小的紋理元素組成。它們是小塊的數據&#xff0c;類似于我們前面討論過的片段和像素。要使用紋理&#xff0c;最經常使用的方式是直接從一個圖像文件載…