作用域和對象釋放的時機和坑

作用域主要是由IServiceScope這個接口來承載的

實現IDisposable接口類型的釋放

  • DI只負責釋放由其創建的對象實例
  • DI在容器或子容器釋放時,釋放由其創建的對象實例

解讀:
對於實現了IDisposable類的實例的對象,我們容器會負責去對其生命週期的管理,當我們使用完畢以後它會去釋放這些對象,但是需要注意的有兩點:

  • DI/容器只會去負責由其創建的對象:也就是說如果我們這個對象是由我們自己去創建出來並放到容器裏的,容器是不負責釋放這個對象的
  • 在容器和子容器釋放時,容器纔會去釋放這些對象:也就是說容器的生命週期與其創建的對象的生命週期是有對應關係的

建議

  • 避免在根容器獲取實現了IDisposable接口的瞬時服務;
  • 避免手動創建實現了IDisposable對象,應該使用容器來管理其生命週期;

解讀:
在這裏面的話我們有兩點是需要注意的:
第一個就是:在根容器,我們最好不要去創建瞬時服務,實現了IDisposable的瞬時服務;
第二個就是:避免手動創建對象 然後塞到容器裏面去,我們應該儘可能的使用容器,來去管理我們的對象的創建和釋放;

瞬時Transient服務

public interface IOrderService{}
public class DisposableOrderService: IOrderService, IDisposable{
    public void Dispose(){
        Console.WriteLine($"DisposableOrderService Disposed:{this.GetHashCode()}");
    }
}
public void ConfigureServices(IServiceCollection services){
    services.AddTransient<IOrderService, DisposableOrderService>();

    services.AddControllers();
}
[HttpGet]
public int Get([FromServices]IOrderService orderService, [FromServices]IOrderService orderService2){  // 獲取了兩次
    Console.WriteLine("接口請求處理結束");
    return 1;
}

執行後有兩個對象被釋放掉了:
image
輸出結果有兩次釋放,每次釋放的對象hashcode都不相同,說明每次瞬時註冊的服務獲取到的實例都是不相同的,且都是在整個請求結束後纔會去觸發釋放的;

作用域Scoped服務(重點)

將上述的瞬時註冊改爲作用域Scoped註冊:

//services.AddTransient<IOrderService, DisposableOrderService>();
services.AddScoped<IOrderService>(p => new  DisposableOrderService());
[HttpGet]
public int Get([FromServices]IOrderService orderService, [FromServices]IOrderService orderService2){
    Console.WriteLine("========1========");
    // HttpContext.RequestServices 表示當前請求的容器,是應用程序根容器的一個子容器,每個請求都會創建一個容器
    // 我們在這個子容器下再創建一個子容器scope來獲取我們的服務
    using(IServiceScope scope = HttpContext.RequestServices.CreateScope()){   // 通過 HttpContext.RequestServices 創建 Scoped
        var service = scope.ServiceProvider.GetService<IOrderService>();
        //var service2 = scope.ServiceProvider.GetService<IOrderService>();   // 即使你加上這句,輸出結果還是2條釋放
    }
    Console.WriteLine("========2========");
    Console.WriteLine("接口請求處理結束");
    return 1;
}

image
每次請求都獲取到兩個釋放,意味我們每創建一個Scope的作用域,每個作用域內我們可以是單例的;

單例Singleton服務

將上述的瞬時註冊改爲單例Singleton註冊:

//services.AddTransient<IOrderService, DisposableOrderService>();
services.AddSingleton<IOrderService>(new  DisposableOrderService());
//services.AddScoped<IOrderService>(p => new  DisposableOrderService());
[HttpGet]
public int Get([FromServices]IOrderService orderService, [FromServices]IOrderService orderService2){
    Console.WriteLine("========1========");
    // HttpContext.RequestServices 表示當前請求的根容器,是應用程序根容器的一個子容器,每個請求都會創建一個容器
    // 我們在這個子容器下再創建一個子容器來獲取我們的服務
    using(IServiceScope scope = HttpContext.RequestServices.CreateScope()){
        var service = scope.ServiceProvider.GetService<IOrderService>();
        var service2 = scope.ServiceProvider.GetService<IOrderService>();
    }
    Console.WriteLine("========2========");
    return 1;
}

image
發現單例模式是沒有釋放的;

當我們使用單例時,其服務的實例是我們自己創建的話:

var servi = new  DisposableOrderService();
services.AddSingleton<IOrderService>(servi);
[HttpGet]
public int Get([FromServices]IOrderService orderService, 
        [FromServices]IOrderService orderService2,
		[Fromservices]IHostApplicationLifetime hostApplicationLifetime,
		[FromQuery] stop){
    Console.WriteLine("========1========");
    // HttpContext.RequestServices 表示當前請求的根容器,是應用程序根容器的一個子容器,每個請求都會創建一個容器
    // 我們在這個子容器下再創建一個子容器來獲取我們的服務
    using(IServiceScope scope = HttpContext.RequestServices.CreateScope()){
        var service = scope.ServiceProvider.GetService<IOrderService>();
        var service2 = scope.ServiceProvider.GetService<IOrderService>();
    }
    Console.WriteLine("========2========");

    if(stop) hostApplicationLifetime.StopApplication();   // 停止應用程序
    return 1;
}

上述代碼運行時,當我們觸發停止應用程序後,我們是不會得到任何釋放的輸出,因爲其創建的實例不是由容器來完成的,容器自然也不會管理到其釋放(不會去管理對象生命週期的);
image
但是改成如下後:

services.AddSingleton<IOrderService, DisposableOrderService>();

image
當我們觸發停止應用程序後,我們得到了一個釋放的輸出;

當我們註冊一個瞬時服務:

public void ConfigureService(IServiceCollection services){
    services.AddTransient<IOrderService, DisposableOrderService>();
}

同時,在 Configure 方法中 從根容器獲取瞬時服務時:

public void Configure(IApplicationBuilder app, IWebHostEnvironment env){
    // 從根容器獲取瞬時服務
    var s = app.ApplicatonServices.GetService<IOrderService>();
    ...
}

這意味着我們會在根容器去持續的創建我們的IOrderService,但是由於根容器只會在應用程序整個退出時回收,也就意味着我們的這些對象會直積累在我們的應用程序內;
上述代碼運行後,只有在項目退出時纔會釋放回收;
即:我們實現了IDisposable接口的服務,如果是註冊瞬時的,我們又在根容器去做操作,它會一直保持到我們的應用程序退出的時候才能夠被回收掉,所以說我們瞬時的服務,如果我們是實現了IDisposable的接口,就不建議使用根容器來去創建我們的對象;

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章