跟我一起學.NetCore之依賴注入作用域和對象釋放


前言

    上一小節簡單闡述了依賴注入及Asp.NetCore中自帶依賴注入組件的常規用法,其中提到容器管控了自己創建對象的生命周期,包含了三種生命周期:Singleton、Scoped、Transient, 對於Singleton、Transient相對於Scoped來說比較好理解,其實這里面有一個作用域的概念,也可以理解為根容器和子容器的范圍;上一小節中有一個例子中說到,當注入的生命周期為Scoped的時,在同一個請求內,注入的對象都是同一個,這里Asp.NetCore將每個請求作為了一個作用域,在此作用域內,生命周期為Scoped的對象就是同一個;下面簡單說說作用域和對象釋放的常規知識點;

 

正文

    作用域這里可以理解為服務范圍,由IServiceScope承載,如其源代碼所示,每一個服務范圍內都一個根容器,如下所示:

// 摘要:
    //     /// The System.IDisposable.Dispose method ends the scope lifetime. Once Dispose
    //     /// is called, any scoped services that have been resolved from /// Microsoft.Extensions.DependencyInjection.IServiceScope.ServiceProvider
    //     will be /// disposed. ///
    public interface IServiceScope : Object, IDisposable
    {
        // IServiceProvider 即代表容器
        //
        // 摘要:
        //     /// The System.IServiceProvider used to resolve dependencies from the scope.
        //     ///
        IServiceProvider ServiceProvider
        {
            get;
        }
    }

    每一個服務范圍內可以通過自身IServiceProvider創建對應的子作用域,而任何一個子作用域的IServiceProvider都有對根容器的引用,如下圖結構:

    每一個IServiceProvider中都會將其創建的對象存入列表中,針對於繼承與IDisposable接口的類型對象會單獨存放在一個列表中,當作用域IServiceScope對象的Dispose方法被調用時,最終對應IServicePorvider對應的對象列表也會清空,針對於繼承與IDisposable類型的對象會調用其Dispose方法;最后導致對應作用域下容器創建的對象成為垃圾對象,被GC給回收;

    在Asp.NetCore中,框架默認將其分有根作用域(與引用程序同生命周期)和請求作用域(每一個請求一個作用域),通常也會稱其為根容器和請求容器,名稱分別為ApplicationServices和RequestServices,在程序中獲取方式如下:

  •     通過IApplicationBuilder對象可以獲取ApplicationServices

  •     通過HttpContext.RequestServices獲取RequestServices

     

    作用域簡單舉例,在請求作用域下再創建子作用域,分別查看對應不同生命周期對象是否一致,新建一個WebApi項目,針對三個生命周期添加了對應文件,結構如下:

    

對應紅框內的文件內容如下:

       細心的小伙伴可能會看到,每個實現類里面都繼承了IDisposable接口,這主要是后面顯示釋放用的,這里先不管;編輯好以上內容之后就將其進行注冊,如下:

    接下來就是使用了,這里通過這Action中使用,在當前的請求作用域下創建子作用域,對比每一個生命周期對象,如代碼所示:

[HttpGet]
public string Get([FromServices]ITestSingleton testSingleton, [FromServices]ITestSingleton testSingleton1,
                  [FromServices]ITestScoped testScoped, [FromServices]ITestScoped testScoped1,
                  [FromServices]ITestTransient testTransient, [FromServices]ITestTransient testTransient1)
{
    //獲取請求作用域(請求容器)
    var requestServices = HttpContext.RequestServices;
    //在請求作用域下創建子作用域
    using(IServiceScope scope = requestServices.CreateScope())
    {
        //在子作用域中通過其容器獲取注入的不同生命周期對象
        ITestSingleton testSingleton11 = scope.ServiceProvider.GetService<ITestSingleton>();
        ITestScoped testScoped11 = scope.ServiceProvider.GetService<ITestScoped>();
        ITestTransient testTransient11 = scope.ServiceProvider.GetService<ITestTransient>();
​
        ITestSingleton testSingleton12 = scope.ServiceProvider.GetService<ITestSingleton>();
        ITestScoped testScoped12 = scope.ServiceProvider.GetService<ITestScoped>();
        ITestTransient testTransient12 = scope.ServiceProvider.GetService<ITestTransient>();
        Console.WriteLine("================Singleton=============");
        Console.WriteLine($"請求作用域的ITestSingleton對象:{testSingleton.GetHashCode()}");
        Console.WriteLine($"請求作用域的ITestSingleton1對象:{testSingleton1.GetHashCode()}");
        Console.WriteLine($"請求作用域下子作用域的ITestSingleton11對象:{testSingleton11.GetHashCode()}");
        Console.WriteLine($"請求作用域下子作用域的ITestSingleton12對象:{testSingleton12.GetHashCode()}");
        Console.WriteLine("================Scoped=============");
        Console.WriteLine($"請求作用域的ITestScoped對象:{testScoped.GetHashCode()}");
        Console.WriteLine($"請求作用域的ITestScoped1對象:{testScoped1.GetHashCode()}");
        Console.WriteLine($"請求作用域下子作用域的ITestScoped11對象:{testScoped11.GetHashCode()}");
        Console.WriteLine($"請求作用域下子作用域的ITestScoped12對象:{testScoped12.GetHashCode()}");
        Console.WriteLine("================Transient=============");
        Console.WriteLine($"請求作用域的ITestTransient對象:{testTransient.GetHashCode()}");
        Console.WriteLine($"請求作用域的ITestTransient1對象:{testTransient1.GetHashCode()}");
        Console.WriteLine($"請求作用域下子作用域的ITestTransient11對象:{testTransient11.GetHashCode()}");
        Console.WriteLine($"請求作用域下子作用域的ITestTransient12對象:{testTransient12.GetHashCode()}");
    }
​
    return "TestServiceScope";
​
}

    運行,進行請求,看打印結果:

  •     對於Singleton來說始終不變,因為其是跟隨根容器生命周期,引用程序退出才釋放;

  •     對於Scoped來說只要在自己的作用域內就是單例的;

  •     對於Transient來說始終創建;

     

    以上一直在說釋放,下面利用繼承IDisposable接口釋放時會調用對應Dispoable方法的原理簡單演示各個生命周期的釋放時機;在Controller中增加幾個Action方法,如下:

    這里使用IHostApplicationLifetime中的StopApplication模擬關閉程序釋放單例下的對象;運行看效果:

 

    使用坑:不要從根容器中獲取Transient生命周期的對象,因為通過根容器創建的對象不會回收,除非等到應用程序退出,這樣會導致內存泄露;如下演示:

    

新增Action方法:

運行,發送請求看結果:

 

總結

    作用域及對象釋放就簡單說這么多,容器只管理自己創建出來的對象生命周期;下一節說說使用第三方組件擴展依賴注入功能;


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM