ASP.NET Core 源碼閱讀筆記(2) ---Microsoft.Extensions.DependencyInjection生命週期管理

    在上一篇文章中咱們主要分析了ASP.NET Core默認依賴注入容器的存儲和解析,這一篇文章主要補充一下上一篇文章忽略的一些細節:有關服務回收的問題,即服務的生命週期問題。有關源碼能夠去GitHub上找到。緩存

    此次的主角就是ServiceProvider一人,全部有關生命週期的源碼幾乎都集中在ServiceProvider.cs這個文件中。ide

    咱們知道服務的生命週期由三種,分別是:函數

  1. Transient
  2. Scoped
  3. Singleton

    首先給出個人結論:這三種生命週期類別本質上沒有區別,服務的生命週期都是由提供服務的容器,即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標記爲垃圾對象。

------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

    總結一下:

  1. ServiceProvider負責回收服務,其中_transientDisposables字段負責transient服務,_resolvedServices字段記錄Scoped服務,用於緩存和釋放。
  2. Singleton服務就是Scoped服務,只不過是應用程序的第一個ServiceProvider(root)的Scoped服務。每個在應用程序中出現的ServiceProvider都會保持對root的引用,要解析Singleton服務時,直接交給root處理。
  3. ServiceProvider的Dispose方法對transientDisposables和_resolvedServices一視同仁,因此除了transient服務不會在實例化以前查詢是否有緩存以外,其餘的都和Scoped服務沒區別。
  4. 服務的回收主要和服務的建立者ServiceProvider的回收相關(依賴於它的Dispose方法),爲了不內存泄漏,可使用using 語法及時釋放服務。
  5. 這一點在正文中沒有提到。Dispose()不是Finalize(),就算你顯式地調用某個服務的Dispose方法,它的內存也不會釋放掉,由於ServiceProvider還保持這對它的引用。CLR runtime規定只有當某個對象不可達時,纔會標記爲垃圾對象,纔會被GC回收。
相關文章
相關標籤/搜索