首页 > 其他分享 >.NET Core 依赖注入 - IServiceProvider和IServiceScope

.NET Core 依赖注入 - IServiceProvider和IServiceScope

时间:2024-02-10 19:22:51浏览次数:18  
标签:容器 Singleton IServiceScope Core 实例 IServiceProvider GetRequiredService

  要说起.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

相关文章

  • 基于.NetCore开发博客项目 StarBlog - (31) 发布和部署
    前言StarBlog第一期规划的功能基本完成了,我想着在春节前应该可以把第一期的系列文章完结掉,那么在差缺补漏阶段就剩下开发项目的最后一个环节——部署了。PS:事实上,还有一个很重要但又经常被略过的测试环节我们没有提到,因为时间关系,第一期规划我没有写单元测试和集成测试,在开......
  • Asp-Net-Core学习笔记:4.Blazor-Server入门
    本来今天开始是有其他的安排了,也没办法抽出那么多时间来学NetCore,不过我想做事情有始有终吧,除了gRPC还没跑起来之外,Blazor这部分也了解了一点,官网地址:https://dotnet.microsoft.com/apps/aspnet/web-apps/blazor目前来说还不是很完善,真正的离线单页应用还处于预览版阶段。Blazo......
  • Asp-Net-Core学习笔记:5.构建和部署
    从上次开始学习Asp.NetCore以来,一直都是玩一玩,还没有什么机会用来实战,最近单位有个新的小项目,于是我用Asp.NetCore来尝尝新,结果也是非常OK,熟悉之后开发效率感觉和Django基本没差。那么进入正题,本文简单说说Asp.NetCore应用的构建和部署。构建构建是使用build命令,不过一般我......
  • Asp-Net-Core学习笔记:WebApi开发实践
    前言用AspNetCore做Api开发也有一段时间了,正好年底在做总结,做一个WebApi开发实践笔记。暂时想到的一些技术关键词,同时也作为本文的大纲,现在对这套技术体系的了解还不够深入,以后会持续更新这个Api开发实践~身份认证:JwtBearer分页:X.PagedList缓存ResponseCacheRedis:St......
  • Asp-Net-Core学习笔记:3.使用SignalR实时通信框架开发聊天室
    SignalR牛刀小试在MVP杨老师的博客里看到这么个东西,我还以为是NetCore3才推出的新玩意,原来是已经有很多年的历史了,那看来还是比较成熟的一个技术了。简介SignalR是一个.NETCore/.NETFramework的开源实时框架,SignalR的可使用WebSocket,ServerSentEvents和LongPolling......
  • Asp-Net-Core学习笔记:部署,早知道,还是docker,以及一点碎碎念
    前言AspNetCore技术栈在我们团队里的使用也有一段时间了,之前的部署方式一直是本地编译之后上传可执行文件到服务器,使用supervisor来管理进程这种很原始的方式。参考之前的文章:对于小项目来说尚可,够用,但是存在几个问题:每次更新花费的时间太长了,无论是Framework-Dependent还是S......
  • Asp .Net Core 系列:Asp .Net Core 集成 Panda.DynamicWebApi
    目录简介Asp.NetCore集成Panda.DynamicWebApi配置原理什么是POCOController?POCO控制器原理ControllerFeatureProvider实现自定义判断规则IApplicationModelConventionPanda.DynamicWebApi中的实现ConfigureApiExplorer()ConfigureSelector()ConfigureParameters()简介Panda......
  • Linux下gdb如何调试coredump文件
    目录简介示例简介在Linux下,你可以使用GNU调试器(GDB)来调试coredump文件。Coredump文件是在程序崩溃时由操作系统生成的,它包含了程序崩溃时的内存内容、寄存器状态和其他相关信息。下面是在Linux下使用GDB调试coredump文件的步骤:确保你的系统已经安装了GDB。如果没有安装,你......
  • Mybatis Plus java.lang.NoSuchMethodError: com.baomidou.mybatisplus.core.toolkit.
    问题描述在进行SpringBoot整合MybatisPlus时提示10:49:08.390[restartedMain]DEBUGorg.springframework.boot.context.logging.ClasspathLoggingApplicationListener-Applicationfailedtostartwithclasspath:[file:/D:/%e7%99%be%e5%ba%a6%e7%bd%91%e7%9b%98/Vue......
  • Asp .Net Core 系列:Asp .Net Core 集成 NLog
    简介NLog是一个基于.NET平台编写的日志记录类库,它可以在应用程序中添加跟踪调试代码,以便在开发、测试和生产环境中对程序进行监控和故障排除。NLog具有简单、灵活和易于配置的特点,支持在任何一种.NET语言中输出带有上下文的调试诊断信息,并能够将这些信息发送到一个或多个输出目标......