<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          終于弄明白了 Singleton,Transient,Scoped 的作用域是如何實現(xiàn)的

          共 6081字,需瀏覽 13分鐘

           ·

          2020-09-04 15:37

          一:背景

          1. 講故事

          前幾天有位朋友讓我有時間分析一下 aspnetcore 中為什么向 ServiceCollection 中注入的 Class 可以做到 Singleton,Transient,Scoped,挺有意思,這篇就來聊一聊這一話題,自從 core 中有了 ServiceCollection, 再加上流行的 DDD 模式,相信很多朋友的項目中很少能看到 new 了,好歹 spring 十幾年前就是這么干的。

          二:Singleton,Transient,Scoped 基本用法

          分析源碼之前,我覺得有必要先介紹一下它們的玩法,為方便演示,我這里就新建一個 webapi 項目,定義一個 interface 和 concrete ,代碼如下:

              public class OrderService : IOrderService
          {
          private string guid;

          public OrderService()
          {
          guid = $"時間:{DateTime.Now}, guid={ Guid.NewGuid()}";
          }

          public override string ToString()
          {
          return guid;
          }
          }

          public interface IOrderService
          {
          }

          1. AddSingleton

          正如名字所示它可以在你的進程中保持著一個實例,也就是說僅有一次實例化,不信的話代碼演示一下哈。


          public class Startup
          {

          public void ConfigureServices(IServiceCollection services)
          {
          services.AddControllers();

          services.AddSingleton();
          }
          }

          [ApiController]
          [Route("[controller]")]
          public class WeatherForecastController : ControllerBase
          {
          IOrderService orderService1;
          IOrderService orderService2;

          public WeatherForecastController(IOrderService orderService1, IOrderService orderService2)
          {
          this.orderService1 = orderService1;
          this.orderService2 = orderService2;
          }

          [HttpGet]
          public string Get()
          {
          Debug.WriteLine($"{this.orderService1}\r\n{this.orderService2} \r\n ------");
          return "helloworld";
          }
          }

          接著運行起來多次刷新頁面,如下圖:

          可以看到,不管你怎么刷新頁面,guid都是一樣,說明確實是單例的。

          2. AddScoped

          正從名字所述:Scope 就是一個作用域,那在 webapi 或者 mvc 中作用域是多大呢?對的,就是一個請求,當然請求會穿透 Presentation, Application, Repository 等等各層,在穿層的過程中肯定會有同一個類的多次注入,那這些多次注入在這個作用域下維持的就是單例,如下代碼所示:


          public void ConfigureServices(IServiceCollection services)
          {
          services.AddControllers();

          services.AddScoped();
          }

          運行起來多次刷新頁面,如下圖:

          很明顯的看到,每次刷 UI 的時候,guid都會變,而在同一個請求 (scope) 中 guid 是一樣的。

          3. AddTransient

          前面大家也看到了,要么作用域是整個進程,要么作用域是一個請求,而這里的 Transient 就沒有作用域概念了,注入一次 實例化一次,不信的話上代碼給你看唄。


          public void ConfigureServices(IServiceCollection services)
          {
          services.AddControllers();

          services.AddTransient();
          }

          從圖中可以看到,注入一次就 new 一次,非常簡單吧,當然了,各有各的應用場景。

          之前不清楚的朋友到現(xiàn)在應該也明白了這三種作用域,接下來繼續(xù)思考的一個問題就是,這種作用域是如何做到的呢?要想回答這個問題,只能研究源代碼了。

          三:源碼分析

          aspnetcore 中的 IOC 容器是 ServiceCollection,你可以向 IOC 中注入不同作用域的類,最后生成 provider,如下代碼所示:


          var services = new ServiceCollection();

          services.AddSingleton();

          var provider = services.BuildServiceProvider();

          1. AddSingleton 的作用域是如何實現(xiàn)的

          通常說到單例,大家第一反應就是 static,但是一般 ServiceCollection 中會有成百上千個 AddSingleton 類型,都是靜態(tài)變量是不可能的,既然不是 static,那就應該有一個緩存字典什么的,其實還真的有這么一個。

          1)RealizedServices 字典

          每一個 provider 內(nèi)部都會有一個 叫做 RealizedServices 的字典,這個 字典 將會在后面充當緩存存在, 如下圖:

          從上圖中可以看到,初始化的時候這個字典什么都沒有,接下來執(zhí)行?var orderService = provider.GetService();?效果如下圖:

          可以看到 RealizedServices 中已經(jīng)有了一個 service 記錄了,接著往下執(zhí)行?var orderService2 = provider.GetService();,最終會進入到?CallSiteRuntimeResolver.VisitCache?方法判斷實例是否存在,如下圖:

          仔細看上面代碼的這句話:?if (!resolvedServices.TryGetValue(callSite.Cache.Key, out obj))?一旦字典存在就直接返回,否則就要執(zhí)行 new 鏈路,也就是?this.VisitCallSiteMain

          綜合來看,這就是為什么可以單例的原因,如果不明白可以拿 dnspy 仔細琢磨琢磨。。。

          2. AddTransient 源碼探究

          前面大家也看到了,provider 里面會有一個 DynamicServiceProviderEngine 引擎類,引擎類中用 字典緩存 來解決單例問題,可想而知,AddTransient 內(nèi)部肯定是沒有字典邏輯的,到底是不是呢?調(diào)試一下唄。

          和單例一樣,最終解析都是由 CallSiteRuntimeResolver 負責的,AddTransient 內(nèi)部會走到 VisitDisposeCache 方法,而這里會一直走?this.VisitCallSiteMain(transientCallSite, context)?來進行 實例的 new 操作,還記得單例是怎么做的嗎?它會在這個 VisitCallSiteMain 上包一層 resolvedServices 判斷,? 繼續(xù)追一下 VisitCallSiteMain 方法吧,這個方法最終會走 CallSiteKind.Constructor 分支調(diào)用你的構造函數(shù),代碼如下:


          protected virtual TResult VisitCallSiteMain(ServiceCallSite callSite, TArgument argument)
          {
          switch (callSite.Kind)
          {
          case CallSiteKind.Factory:
          return this.VisitFactory((FactoryCallSite)callSite, argument);
          case CallSiteKind.Constructor:
          return this.VisitConstructor((ConstructorCallSite)callSite, argument);
          case CallSiteKind.Constant:
          return this.VisitConstant((ConstantCallSite)callSite, argument);
          case CallSiteKind.IEnumerable:
          return this.VisitIEnumerable((IEnumerableCallSite)callSite, argument);
          case CallSiteKind.ServiceProvider:
          return this.VisitServiceProvider((ServiceProviderCallSite)callSite, argument);
          case CallSiteKind.ServiceScopeFactory:
          return this.VisitServiceScopeFactory((ServiceScopeFactoryCallSite)callSite, argument);
          }
          throw new NotSupportedException(string.Format("Call site type {0} is not supported", callSite.GetType()));
          }

          最終由 VisitConstructor 對我的實例代碼的構造函數(shù)進行調(diào)用,所以你應該理解了為啥每次注入都會new一次。如下圖:

          3. AddScoped 源碼探究

          當你明白了 AddSingleton, AddTransient 的原理,我想 Scoped 也是非常容易理解的,肯定是一個 scoped 一個 RealizedServices 對吧,不信的話繼續(xù)上代碼哈。


          static void Main(string[] args)
          {
          var services = new ServiceCollection();

          services.AddScoped();

          var provider = services.BuildServiceProvider();

          var scoped1 = provider.CreateScope();

          var scoped2 = provider.CreateScope();

          while (true)
          {
          var orderService = scoped1.ServiceProvider.GetService();

          var orderService2 = scoped2.ServiceProvider.GetService();

          Console.WriteLine(orderService);

          Thread.Sleep(1000);
          }
          }

          然后看一下 scoped1 和 scoped2 是不是都存在獨立的緩存字典。

          從圖中可以看到,scoped1 和 scoped2 中的 ResolvedServices 擁有不用的count,也就說明兩者是獨立存在的,相互不影響。

          四:總結

          很多時候大家都這么習以為常的用著,突然有一天被問起還是有點懵逼的,所以時常多問自己幾個為什么還是很有必要的哈???。

          往期精彩回顧




          【推薦】.NET Core開發(fā)實戰(zhàn)視頻課程?★★★

          .NET Core實戰(zhàn)項目之CMS 第一章 入門篇-開篇及總體規(guī)劃

          【.NET Core微服務實戰(zhàn)-統(tǒng)一身份認證】開篇及目錄索引

          Redis基本使用及百億數(shù)據(jù)量中的使用技巧分享(附視頻地址及觀看指南)

          .NET Core中的一個接口多種實現(xiàn)的依賴注入與動態(tài)選擇看這篇就夠了

          10個小技巧助您寫出高性能的ASP.NET Core代碼

          用abp vNext快速開發(fā)Quartz.NET定時任務管理界面

          在ASP.NET Core中創(chuàng)建基于Quartz.NET托管服務輕松實現(xiàn)作業(yè)調(diào)度

          現(xiàn)身說法:實際業(yè)務出發(fā)分析百億數(shù)據(jù)量下的多表查詢優(yōu)化

          關于C#異步編程你應該了解的幾點建議

          C#異步編程看這篇就夠了

          給我好看

          您看此文用

          ??·?

          秒,轉發(fā)只需1秒呦~

          好看你就

          點點


          瀏覽 31
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          評論
          圖片
          表情
          推薦
          點贊
          評論
          收藏
          分享

          手機掃一掃分享

          分享
          舉報
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  西西444WWW无码视频男男 | 日韩中文字幕在线观看视频 | 人妻人人操人人爽 | 青青草原在线视频网站 | 天天舔天天日 |