作用域和对象释放的时机和坑
作用域主要是由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;
}
执行后有两个对象被释放掉了:
输出结果有两次释放,每次释放的对象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;
}
每次请求都获取到两个释放,意味我们每创建一个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;
}
发现单例模式是没有释放的;
当我们使用单例时,其服务的实例是我们自己创建的话:
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;
}
上述代码运行时,当我们触发停止应用程序后,我们是不会得到任何释放的输出,因为其创建的实例不是由容器来完成的,容器自然也不会管理到其释放(不会去管理对象生命周期的);
但是改成如下后:
services.AddSingleton<IOrderService, DisposableOrderService>();
当我们触发停止应用程序后,我们得到了一个释放的输出;
坑
当我们注册一个瞬时服务:
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的接口,就不建议使用根容器来去创建我们的对象;