在AspNetCore中理解依赖注入生命周期冲突与解决方案
本文详细讲解AspNetCore依赖注入生命周期冲突问题通过BackgroundService后台托管服务示例分析Singleton、Scoped、Transient三种生命周期的区别及注入规则重点说明为什么Singleton不能直接依赖Scoped服务以及如何使用IServiceScopeFactory解决生命周期捕获问题并深入解析Transient为何不会触发生命周期冲突。在AspNetCore中理解依赖注入生命周期冲突在 ASP.NET Core 的日常开发中依赖注入Dependency Injection, DI几乎是贯穿所有功能的核心技术。而在学习 DI 时一个非常重要但又容易被忽略的问题就是 服务生命周期Service Lifetime之间的冲突。这篇文章通过一个简单的 后台托管服务BackgroundService 示例来展示不同生命周期的服务在注入时会出现什么问题以及如何正确解决这种冲突。生命周期的三种类型在 ASP.NET Core 中服务的生命周期有三种类型Transient每次请求都会创建一个新的实例Scoped每次请求都会创建一个新的实例但同一个请求中的不同控制器会共享同一个实例Singleton整个应用程序中只有一个实例所有请求都会共享这个实例而后台托管服务BackgroundService是 ASP.NET Core 中用于在后台运行异步任务的抽象基类。本文中选择它为案例是因为后台托管任务的生命周期是singleton。依赖注入的常用方式在 ASP.NET Core 中依赖注入的常用方式有三种构造函数注入Constructor InjectionpublicclassTestHostedService:BackgroundService{privatereadonlyILoggerTestHostedService_logger;privatereadonlyIIdGenService_idGenService;publicTestHostedService(ILoggerTestHostedServicelogger,IIdGenServiceidGenService){_loggerlogger;_idGenServiceidGenService;}}属性注入Property Injection在注册服务时需要使用 ActivatorUtilities 或 ConfigureServices 配置 才能给它写入属性publicclassMyService{publicILoggerMyService?Logger{get;set;}publicvoidDoWork(){Logger?.LogInformation(Working...);}}3.方法注入Method Injection csharppublicclassMyController:Controller{publicIActionResultIndex([FromServices]IIdGenServiceidGenService){stringididGenService.NewGuid();returnOk(id);}}其中构造函数注入和属性注入是比较常见的而方法注入则比较少见。建议使用构造函数注入因为它更方便更安全。方法和属性注入在这里就是补充说明一下因为它们的使用场景比较少所以这里就不再深入讲解了。示例代码一个依赖 IdGenService 的后台定时任务usingMicrosoft.EntityFrameworkCore;namespaceTestEFc_IEQable_AspNetCore01x02{publicclassTestHostedService:BackgroundService{privatereadonlyILoggerTestHostedService_logger;privatereadonlyIIdGenService_idGenService;privateint_executionCount;publicTestHostedService(ILoggerTestHostedServicelogger,IIdGenServiceidGenService){_loggerlogger;_idGenServiceidGenService;}protectedoverrideasyncTaskExecuteAsync(CancellationTokenstoppingToken){_logger.LogInformation(定时托管服务启动.);awaitDoWork();usingPeriodicTimertimernew(TimeSpan.FromSeconds(10));try{while(awaittimer.WaitForNextTickAsync(stoppingToken)){awaitDoWork();}}catch(OperationCanceledException){_logger.LogInformation(定时托管服务已停止.);}}privateasyncTaskDoWork(){stringidawait_idGenService.NewGuidAsync(5);_logger.LogInformation($生成Id:{id});intcountInterlocked.Increment(ref_executionCount);awaitTask.Delay(TimeSpan.FromSeconds(2));_logger.LogInformation(定时托管服务运行 {Count},count);}}}在Program.cs中注册服务builder.Services.AddHostedServiceTestHostedService();//builder.Services.AddTransientIIdGenService, IdGenService();//builder.Services.AddScopedIIdGenService, IdGenService();//builder.Services.AddSingletonIIdGenService, IdGenService();在这段代码中我们注册了后台托管服务 TestHostedService并使用不同的生命周期类型注册了 IdGenService。重点是 IIdGenService 的生命周期不同会带来不同效果。我们先分别解除注释看看使用情况解除 Scoped 的注释出现了报错运行截图核心原因是ASP.NET Core 的 后台托管服务HostedService默认是 Singleton单例DI 不允许 生命周期长的服务依赖生命周期短的服务因为这会产生所谓的捕获capture一个比自身短生命周期的依赖可能导致依赖被提前销毁或状态错误。例如在后台托管服务中直接注入DbContext就会出现这种情况。因为DbContext的生命周期默认是Scoped而导致后台托管服务在使用完这一个DbContext后却还一直持有占用数据库连接导致数据库连接池耗尽。Singleton 服务 在整个程序生命周期都活着它不能依赖随时都可能被释放的 Transient 或 Scoped 服务。然后我们把Scoped注释掉再解除Singleton的注释发现正常运行运行截图因为这时IdGenService是与托管服务TestHostedService是同一个生命周期所以没有问题。上面说了长的生命周期不允许依赖短的生命周期那我们猜一下解除 Transient 的注释会发生什么运行截图(⊙o⊙)不对怎么是正常的我去查了一下原来在ASP.NET Core 的 DI 系统中Transient 是不受生命周期“捕获”规则限制的。微软官方定义如下简化Transient 服务每次请求都会重新创建容器允许它被任何生命周期的服务注入。也就是说. Transient 本身没有“作用域”. 它也不依赖容器来管理生命周期. 所以不会产生“被提前释放”的风险因此单例依赖 Transient 是安全的没有生命周期冲突那为什么大家都以为 Transient 会报错因为很多教程、中文文章、视频讲解都把 Scoped 和 Transient 混在一起以为这两者都「生命周期短 → 都不能给单例用」。但实际情况是生命周期是否允许被 Singleton 依赖原因Singleton✔同生命周期 / 更长Transient✔不依赖 Scope由容器直接 newScoped❌必须在 Scope 内创建Http Request那如果我们非要在 Singleton 中注入 Scoped 呢解决方案使用 using 和 IServiceScopeFactory 来创建一个临时的 Scoped 作用域usingMicrosoft.EntityFrameworkCore;namespaceTestEFc_IEQable_AspNetCore01x02{publicclassTestHostedService:BackgroundService{privatereadonlyILoggerTestHostedService_logger;privatereadonlyIServiceScopeFactory_serviceScopeFactory;//看这里privateint_executionCount;publicTestHostedService(ILoggerTestHostedServicelogger,IServiceScopeFactoryserviceScopeFactory){_loggerlogger;_serviceScopeFactoryserviceScopeFactory;}protectedoverrideasyncTaskExecuteAsync(CancellationTokenstoppingToken){_logger.LogInformation(定时托管服务启动.);awaitDoWork();usingPeriodicTimertimernew(TimeSpan.FromSeconds(10));try{while(awaittimer.WaitForNextTickAsync(stoppingToken)){awaitDoWork();}}catch(OperationCanceledException){_logger.LogInformation(定时托管服务已停止.);}}privateasyncTaskDoWork(){//看这里///using和IServiceScopeFactoryusingvarscope_serviceScopeFactory.CreateScope();vardateContextscope.ServiceProvider.GetRequiredServiceDateContext();///using和IServiceScopeFactory/vartoDoListawaitdateContext.ToDoListTable.Include(tt.TransactionNodes).FirstOrDefaultAsync(xx.Id1ca);_logger.LogInformation($ToDoList Id:{toDoList.Id}, Title:{toDoList.ListTitle}, Description:{toDoList.ListDescription}, State:{toDoList.State});foreach(varitemintoDoList.TransactionNodes){_logger.LogInformation($TransactionNode Id:{item.Id}, SerialNumber:{item.SerialNumber}, Content:{item.Content}, State:{item.State});}intcountInterlocked.Increment(ref_executionCount);awaitTask.Delay(TimeSpan.FromSeconds(2));_logger.LogInformation(定时托管服务运行 {Count},count);}}}这样就解决了运行截图
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2418905.html
如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!