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

来源:http://www.cnblogs.com/bill-shooting/archive/2016/06/01/5550198.html
-Advertisement-
Play Games

在上一篇文章中我們主要分析了ASP.NET Core預設依賴註入容器的存儲和解析,這一篇文章主要補充一下上一篇文章忽略的一些細節:有關服務回收的問題,即服務的生命周期問題。有關源碼可以去GitHub上找到。 這次的主角就是ServiceProvider一人,所有有關生命周期的源碼幾乎都集中在Serv ...


    在上一篇文章中我們主要分析了ASP.NET Core預設依賴註入容器的存儲和解析,這一篇文章主要補充一下上一篇文章忽略的一些細節:有關服務回收的問題,即服務的生命周期問題。有關源碼可以去GitHub上找到。

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

    我們知道服務的生命周期由三種,分別是:

  1. Transient
  2. Scoped
  3. Singleton

    首先給出我的結論:這三種生命周期類別本質上沒有區別,服務的生命周期都是由提供服務的容器,即ServiceProvider的生命周期決定的,一個ServiceProvider被回收之後,所有由它產生的Service也隨之被回收。由此看來,一個ServiceProvider起了一個ServiceScoped的作用,其實就是這樣,ServiceScope本質上就是一個ServiceProvider。

 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都被回收了,那麼整個程式就該結束了。

 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)創建即可。

    既然Singleton就是Scoped,那我們就把重點放在Scoped和Transient上。下麵是ServiceProvider中有關Scoped和Transient的源碼。

 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回收。

    再看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)是不會去解析Transient服務的。它可以創建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回收。

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 聲明:本系列為原創,分享本人現用框架,未經本人同意,禁止轉載!http://yuangang.cnblogs.com 希望大家好好一步一步做,所有的技術和項目,都毫無保留的提供,希望大家能自己跟著做一套,還有,請大家放心,只要大家喜歡,有人需要,絕對不會爛尾,我會堅持寫完~ 如果你感覺文章有幫助,點 ...
  • 1.參數化查詢模糊查詢 sql語句: create proc procegDataAp( @UserName nvarchar(50))asselect * from users where userName=@UserName 給參數賦值 1 <%@ Page Language="C#" Auto ...
  • 1.SQL註入:SQL註入攻擊是web應用程式的一種安全漏洞,可以將不安全的數據提交給運用程式,使應用程式在伺服器上執行不安全的sql命令。使用該攻擊可以輕鬆的登錄運用程式。 例如:該管理員賬號密碼為xiexun,該sql的正確語句應該為: 如果在沒有做任何處理的情況下,在登錄名文本框中輸入(xux ...
  • 常用快捷鍵 自動生成頭部註釋 代碼片段 NuGet Team Foundation 常用的VS快捷鍵 查看與設置快捷鍵 一般在菜單裡面我們直接就可以看到一些功能的快捷鍵。另外,可以依次通過 菜單欄-工具-選項-環境-鍵盤 中查看和設置對應功能的快捷鍵 推薦幾個我比較常用的快捷鍵 我用的是VS2015 ...
  • 1、頁面後臺代碼添加如下靜態變數: 2、在處理數據的開始,初始化total和startTime變數: 3、在處理數據過程中,不斷累加cur: 4、前端每隔200毫秒獲取進度: 5、後臺計算進度: 效果圖(文字錯了,不是“導入進度”,而是“數據處理進度:”): ...
  • 公司業務量比較大,接了很多項目,為了縮短開發周期老闆讓我牽頭搭建了一個敏捷開發框架。 我們主要的業務是做OA、CRM、ERP一類的管理系統,一個通用的後臺搭出來,再配合一些快速開發的組件開發效率能提高很多。 另外老闆一再強調要支持APP開發,一次開發能部署到安卓和IOS上。 作為開篇之作,先介紹一下 ...
  • 定製路由系統 路由系統是靈活可配置的,當然還可以通過下麵這兩種方式定製路由系統,來滿足其他需求。 1、 通過創建自定義的RouteBase實現; 2、 通過創建自定義路由處理程式實現。 創建自定義的RouteBase實現 創建自定義的RouteBase實現,需要實現一個RouteBase的派生類,而 ...
  • 同Winsock1相比,Winsock2最明顯的就是支持了Raw Socket套接字類型,使用Raw Socket,可把網卡設置成混雜模式,在這種模式下,我們可以收到網路上的IP包,當然包括目的不是本機的IP包,通過原始套接字,我們也可以更加自如地控制Windows下的多種協議,而且能夠對網路底層的 ...
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...