要说起.NET Core,我想没有人会不知道依赖注入(DI),同时,这也真是一个被说烂的话题,如果你关注.NET Core,总会有人不厌其烦的给你讲什么是依赖,什么是注入,什么是控制反转,同时会给你举例 .NET Core DI三种生命周期(Transient,Scoped还有Singleton),并且通过打印hashcode的方式来说明彼此之间的区别。
如果你不知道我上面列举的是什么,那么去搜搜吧,这些知识虽然说烂了,但对DI的理解非常有用,正如标题所言,今天是闲聊,聊什么呢,就先从IServiceProvider和IServiceScope聊起吧,如果有理解不对的地方,欢迎指正。
IServiceProvider和IServiceScope
http://asp.net core无论是启动还是处理请求,所需要的实例都是由IServiceProvider提供的,很多小伙伴以为IServiceProvider只有一种。其实不然,总体来说,IServiceProvider有两种,一种是在根容器的(root scope)IServiceProvider(一般称为ApplicationServices),您可以认为它是IServiceProvider中的大佬,它除了提供管道构建过程中需要的实例外,处理创建的实例的事儿人家基本不参合,它的生命周期是与当前应用的生命周期一致的。
那另一种IServiceProvider是做什么的呢?它主要负责每次请求的实例创建等工作,我们一般称它为RequestServices。
我们再来说说IServiceScope,这个表示的是服务范围,这个服务范围是由IServiceScopeFactory来创建的,源码如下:
1 public static IServiceScope CreateScope(this IServiceProvider provider) 2 { 3 return provider.GetRequiredService<IServiceScopeFactory>().CreateScope(); 4 }
任何一个IServiceProvider对象都可以通过CreateScope这个扩展方法创建一个IServiceScope对象,相当于创建了一个子容器,每个新建的子容器中都有自己的IServiceProvider对象,值得注意的是,每个新建的子容器中的IServiceProvider对象,都保留着对根容器的引用。
public IServiceScope RootScope { get { return (IServiceScope) this.Root; } }
在http://asp.net core中,他们之间的关系大概是这样的:
在http://asp.net core中,每个请求都会创建一个scope,这个请求过程中由IServiceProvider创建的服务实例都保存在保存在当前IServiceProvider对象上,所以可以保证当前范围内保证提供的实例是单例的。
也许有的小伙伴会好奇,知道根容器和子容器的两种IServiceProvider有什么用?日常使用确实感觉不到他们的存在,当你感知到它们的存在的时候,往往是在异常信息中,比如有的小伙伴试图使用根容器的IServiceProvider去创建scoped作用域的对象的实例,那直接就抛异常了,Scoped services aren't directly or indirectly resolved from the root service provider这样的报错信息不知道有没有见过,我上面说过,根容器里的IServiceProvider相当于大佬,你指使大佬干这样的杂活,大佬很不开心。
当然,这才刚开始,我们继续聊
Transient,Scoped和Singleton
在我们了解完IServiceProvider和IServiceScope之间的关系后,我们回过头再聊聊Transient,Scoped和Singleton。
先说说Singleton,它和其他两个不一样,它跟大佬混,也就是它保存在根容器的IServiceProvider对象上。还记得我刚才说的每个新建的子容器中的IServiceProvider对象,都保留着对根容器的引用吗?这样的机制,就为这些同根IServiceProvider对象提供了真正单例的保证。
Scoped,上面也提及了,它能在当前范围内保证提供的实例是单例的
至于Transient嘛,每次请求,IServiceProvider总是创建一个新的请求。
GetService vs GetRequiredService
GetService和GetRequiredService是IServiceProvider下的两个扩展方法,用它们可以获取服务的实例,上面提到过,在进行操作的时候,如果该IServiceProvider在根容器中,你要格外的小心。
那么GetService和GetRequiredService有什么区别呢?
如果该服务可用,那么这两个方法表现的行为是一样的,返回的都是serviceType
但如果该服务未被注册,GetService会返回NULL,而GetRequiredService会抛出InvalidOperationException异常。
值得注意的是,这种利用注入的IServiceProvider来获取其他依赖的服务实例的行为,是一种反模式,它隐藏了类之间的依赖关系,如果可以,尽量不用。
如果不得不用,用哪个呢?我更推荐GetRequiredService,因为相比于GetService,它快速失败,也不用校验null,减少了很多隐藏的问题,也减少了很多不必要的校验工作。
服务实例的释放
这是要聊的最后一个话题了,IServiceProvider除了提供了提供服务实例的功能外,还承担着回收释放的职责,IServiceProvider的释放策略取决于生命周期模式,如果服务实现了IDisposable接口的话,该服务会被维护到IServiceProvider自身的需要释放的列表中,但是Singleton不是,Singleton是维护到根容器的IServiceProvider内。对于非根容器来说,IServiceProvider生命周期取决于外层包裹的IServiceScope,当IServiceScope的Dispose方法被调用的时候,IServiceProvider的Dispose也会被调用,之后自身维护需要释放的服务也会逐一被释放。至此,IServiceScope和IServiceProvider生命周期双双终结。
说道这,有没有小伙伴尝试过在多线程中使用GetRequiredService方法?比如这样的写法
1 public HomeController(ILogger<HomeController> logger,IServiceProvider serviceProvider) 2 { 3 _logger = logger; 4 _serviceProvider = serviceProvider; 5 } 6 7 public IActionResult Index() 8 { 9 Task.Run( () => 10 { 11 var _ = _serviceProvider.GetRequiredService<IScopedService>(); 12 }); 13 return View(); 14 }
如果不出意外,您可能会收到cannot access a disposed object这样的异常,根据我上面的描述,您应该猜到是怎么回事了吧。
当然,这问题也比较好解决,稍微改造一下:
1 public HomeController(ILogger<HomeController> logger,IServiceScopeFactory serviceScopeFactory) 2 { 3 _logger = logger; 4 _serviceScopeFactory = serviceScopeFactory; 5 6 } 7 8 public IActionResult Index() 9 { 10 Task.Run( () => 11 { 12 using var scope=_serviceScopeFactory.CreateScope(); 13 var _ = scope.ServiceProvider.GetRequiredService<IScopedService>(); 14 }); 15 return View(); 16 }
话题扯远了,我们接着聊释放,Singleton和Scoped不必多说,一个是应用根容器生命终结释放,一个是请求终结被释放。当然正常情况下是这样的,但也有不正常的情况。
当有一天,我们不小心从根容器中获取Transient或者Scope的实例,比如这样:
那我们期待已久的用完即抛的现象没有发生,直到我终止了程序,它才得以释放…
就像大佬哪天看小弟手下对象不错,一时兴起,直接要人,从此这个对象就感觉自己走上人生巅峰了,俺是大佬的人了,小弟你想像以前那样对我吆五喝六?我呸!
咳咳,从根容器的IServiceProvider获取服务实例,尽管从技术上是可行的,但是这么做会造成内存泄漏,实际会把Transient变成Singleton。
所以正确的写法应该是这样的。
我们最后在说说Singleton。正常情况下,当服务结束,它应该被释放。如下面所示。
一切如预期,对吧,我们换一种注入方式,我们从外部实例化服务,然后进行注入。
我们期待的没有发生,也就是说,我们手工塞进去的对象作为实例,是不会被释放掉的。所以这么做,除非有特殊的理由,要么自己手工释放掉,总之,要多留神没有错。
最后
今儿我们聊了IServiceProvider和IServiceScope,也聊了三个生命周期背后的原理,最后我们讨论了一下资源释放的问题,今儿就聊到这,虽然内容不多,不知不觉也写了好几个小时,已经有点恍惚了。
标签:容器,Singleton,IServiceScope,Core,实例,IServiceProvider,GetRequiredService From: https://www.cnblogs.com/vfphome/p/18012989