在上一篇文章中咱们主要分析了ASP.NET Core默认依赖注入容器的存储和解析,这一篇文章主要补充一下上一篇文章忽略的一些细节:有关服务回收的问题,即服务的生命周期问题。有关源码能够去GitHub上找到。缓存
此次的主角就是ServiceProvider一人,全部有关生命周期的源码几乎都集中在ServiceProvider.cs这个文件中。ide
咱们知道服务的生命周期由三种,分别是:函数
首先给出个人结论:这三种生命周期类别本质上没有区别,服务的生命周期都是由提供服务的容器,即ServiceProvider的生命周期决定的,一个ServiceProvider被回收以后,全部由它产生的Service也随之被回收。由此看来,一个ServiceProvider起了一个ServiceScoped的做用,其实就是这样,ServiceScope本质上就是一个ServiceProvider。ui
1 internal class ServiceScope : IServiceScope 2 { 3 //仅有一个只读的ServiceProvider字段 4 private readonly ServiceProvider _scopedProvider; 5 6 public ServiceScope(ServiceProvider scopedProvider) 7 { 8 _scopedProvider = scopedProvider; 9 } 10 11 public IServiceProvider ServiceProvider 12 { 13 get { return _scopedProvider; } 14 } 15 16 public void Dispose() 17 { 18 _scopedProvider.Dispose(); 19 } 20 }
因此其实也没ServiceScope什么事情,每个范围都是由ServiceProvider控制的。这么一来,Singleton服务和Scoped服务就是同样的,由于每个程序都有一个最初的ServiceProvider,咱们能够叫它root,或者叫它爸爸,其余的全部ServiceProvider都是由root(爸爸)建立的,天然爸爸的范围最大,因此被爸爸建立的Scoped服务就是所谓的Singleton,由于没有比root(爸爸)范围更大的ServiceProvider了。假如root都被回收了,那么整个程序就该结束了。this
1 public ServiceProvider(IEnumerable<ServiceDescriptor> serviceDescriptors) 2 { 3 _root = this; 4 _table = new ServiceTable(serviceDescriptors); 5 6 _table.Add(typeof(IServiceProvider), new ServiceProviderService()); 7 _table.Add(typeof(IServiceScopeFactory), new ServiceScopeService()); 8 _table.Add(typeof(IEnumerable<>), new OpenIEnumerableService(_table)); 9 } 10 11 // This constructor is called exclusively to create a child scope from the parent 12 internal ServiceProvider(ServiceProvider parent) 13 { 14 //注意下面这句代码 15 _root = parent._root; 16 _table = parent._table; 17 }
上面贴出来的是ServiceProvider的两个构造函数,注意第二个构造函数:_root字段引用的是爸爸的根,而不是爸爸。假如ServiceProviderA(SPA)建立了SPB,而SPB建立了SPC,那么SPC的_root字段引用的也是SPA。也就是说,全部ServiceProvider之间不是层状结构,不是咱们熟悉的树结构,而是一种星型结构,应用程序的第一个ServiceProvider在最中间,其余全部的ServiceProvider的_root字段都是引用了第一个ServiceProvider,除了第一个ServiceProvider,其余的ServiceProvider都是平等的。假如SPC要建立一个Singleton类型的服务,那么直接让_root(也就是SPA)建立便可。spa
既然Singleton就是Scoped,那咱们就把重点放在Scoped和Transient上。下面是ServiceProvider中有关Scoped和Transient的源码。code
1 internal class ServiceProvider : IServiceProvider, IDisposable 2 { 3 private readonly ServiceProvider _root; 4 private readonly ServiceTable _table; 5 private bool _disposeCalled; 6 7 //Scoped模式的服务的映射,用于释放服务实例 8 private readonly Dictionary<IService, object> _resolvedServices = new Dictionary<IService, object>(); 9 //一次性服务的Dispose列表 10 private List<IDisposable> _transientDisposables; 11 12 internal IServiceCallSite GetResolveCallSite(IService service, ISet<Type> callSiteChain) 13 { 14 IServiceCallSite serviceCallSite = service.CreateCallSite(this, callSiteChain); 15 if (service.Lifetime == ServiceLifetime.Transient) 16 { 17 return new TransientCallSite(serviceCallSite); 18 } 19 else if (service.Lifetime == ServiceLifetime.Scoped) 20 { 21 return new ScopedCallSite(service, serviceCallSite); 22 } 23 else 24 { 25 return new SingletonCallSite(service, serviceCallSite); 26 } 27 } 28 29 private class TransientCallSite : IServiceCallSite 30 { 31 private readonly IServiceCallSite _service; 32 //Involve方法是关键 33 public object Invoke(ServiceProvider provider) 34 { 35 //触发并放入ServiceProvider的一次性服务释放列表 36 return provider.CaptureDisposable(_service.Invoke(provider)); 37 } 38 //省略Build方法 39 } 40 private class ScopedCallSite : IServiceCallSite 41 { 42 private readonly IService _key; 43 private readonly IServiceCallSite _serviceCallSite; 44 //Invoke方法是关键,省略了其余无关的方法 45 public virtual object Invoke(ServiceProvider provider) 46 { 47 object resolved; 48 //放入ServiceProvider的Scoped服务解析列表 49 lock (provider._resolvedServices) 50 { 51 //若是ResolvedService列表中已经缓存了,就不用再建立 52 if (!provider._resolvedServices.TryGetValue(_key, out resolved)) 53 { 54 resolved = _serviceCallSite.Invoke(provider); 55 provider._resolvedServices.Add(_key, resolved); 56 } 57 } 58 return resolved; 59 } 60 }
从ServiceProvider的GetResolvedCallSite方法能够看出,当咱们要解析一项服务时,先根据服务的生存周期生成不一样的CallSite,不一样CallSite的Invoke方法决定了ServiceProvider怎么管理这些服务。对象
首先看TransientCallSite.Invoke()。里面调用了ServiceProvider的私有方法:CaptureDisposable(),这个方法是捕捉实现了IDisposable接口的服务,若是实现了接口,就将其放入ServiceProvider的_transientDisposables字段中。这个字段顾名思义,是为了释放释放Transient类型的服务而存在的。那若是某个服务没有实现IDisposable接口,那么当服务结束以后ServiceProvider不会保持对它的引用,因为没有变量对它有引用,天然会被GC回收。blog
再看ScopedCallSite.Invoke()。首先是在ServiceProvider的_resolvedServices字段中查找相应的服务,若是能找到,说明以前建立过,就无须再建立了。若是还没建立,就将其放入_resolvedServices字段缓存,以备不时之需。貌似Scoped类型服务没有像Transient服务那样有专门的字段管理Dispose,由于这不须要,_resolvedServices字段既能够做为缓存使用,又能够供Dispose使用。接口
看一下ServiceProvider的Dispose方法:
public void Dispose() { lock (SyncObject) { if (_disposeCalled) { return; } _disposeCalled = true; if (_transientDisposables != null) { foreach (var disposable in _transientDisposables) { disposable.Dispose(); } _transientDisposables.Clear(); } foreach (var entry in _resolvedServices) { (entry.Value as IDisposable)?.Dispose(); } _resolvedServices.Clear(); } }
从上面的方法中能够看出,ServiceProvider对待_resolvedServices和_transientDisposables是同样的,并不会特地将Transient的服务频繁释放几回。Transient服务和Scoped服务惟一的区别就在于Transient服务在实例化以前不会去缓存字段中查找是否已经有缓存了,若是有须要,ServiceProvider就会帮你实例化一个。全部ServiceProvider建立的服务(不管是Transient仍是Scoped)都只会在ServiceProvider释放的时候才会释放。这会带来一个问题:若是一个ServiceProvider长时间不Dispose,那么若是它要解析Transient类型的服务,会占用大量的内存甚至形成内存泄漏,实例越多,对GC也有影响。由此能够想象,ASP.NET Core的第一个ServiceProvider(也就是root)是不会去解析实现了IDisposable接口的Transient服务的(会被root引用)。它能够建立ServiceScope,由它们的ServiceProvider去解析服务。
为了不大量的无用的服务留在内存中,咱们要释放无用的服务,好比在RenderView的时候,有关Route的服务确定已经没用了,为此能够建立不一样的ServiceScope。使用using语句能够正确的释放无用的服务。
1 void DoSthAboutRoute() 2 { 3 using (IServiceScope serviceScope = serviceProvider.GetService<IServiceScopeFactory>().CreateScope()) 4 { 5 IService routeService = serviceScope.ServiceProvider.GetService<IRouteService>(); 6 //.... 7 } 8 }
using 语句在结束时会自动调用serviceScope.ServiceProvider.Dispose()方法,因此全部由该ServiceProvider建立的服务都会被及时的释放掉,此时变量serviceScope已经超出了它的做用域,它会被GC标记为垃圾对象。
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
总结一下: