细聊.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.mzph.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;最经常使用的方式是直接从一个图像文件载…