当前位置: 首页 > news >正文

在AspNetCore中理解依赖注入生命周期冲突与解决方案

本文详细讲解AspNetCore依赖注入生命周期冲突问题,通过BackgroundService后台托管服务示例,分析Singleton、Scoped、Transient三种生命周期的区别及注入规则,重点说明为什么Singleton不能直接依赖Scoped服务,以及如何使用IServiceScopeFactory解决生命周期捕获问题,并深入解析Transient为何不会触发生命周期冲突。

在AspNetCore中理解依赖注入生命周期冲突

在 ASP.NET Core 的日常开发中,依赖注入(Dependency Injection, DI)几乎是贯穿所有功能的核心技术。而在学习 DI 时,
一个非常重要但又容易被忽略的问题就是 服务生命周期(Service Lifetime)之间的冲突。

这篇文章通过一个简单的 后台托管服务(BackgroundService) 示例,来展示不同生命周期的服务在注入时会出现什么问题,以及如何正确解决这种冲突。

生命周期的三种类型

在 ASP.NET Core 中,服务的生命周期有三种类型:

  1. Transient:每次请求都会创建一个新的实例
  2. Scoped:每次请求都会创建一个新的实例,但同一个请求中的不同控制器会共享同一个实例
  3. Singleton:整个应用程序中只有一个实例,所有请求都会共享这个实例

而后台托管服务(BackgroundService)是 ASP.NET Core 中用于在后台运行异步任务的抽象基类。
本文中选择它为案例是因为后台托管任务的生命周期是singleton。

依赖注入的常用方式

在 ASP.NET Core 中,依赖注入的常用方式有三种:

  1. 构造函数注入(Constructor Injection)
publicclassTestHostedService:BackgroundService{privatereadonlyILogger<TestHostedService>_logger;privatereadonlyIIdGenService_idGenService;publicTestHostedService(ILogger<TestHostedService>logger,IIdGenServiceidGenService){_logger=logger;_idGenService=idGenService;}}
  1. 属性注入(Property Injection)
    在注册服务时,需要使用 ActivatorUtilities 或 ConfigureServices 配置 才能给它写入属性
publicclassMyService{publicILogger<MyService>?Logger{get;set;}publicvoidDoWork(){Logger?.LogInformation("Working...");}}```3.方法注入(Method Injection) ```csharppublicclassMyController:Controller{publicIActionResultIndex([FromServices]IIdGenServiceidGenService){stringid=idGenService.NewGuid();returnOk(id);}}

其中构造函数注入和属性注入是比较常见的,而方法注入则比较少见。建议使用构造函数注入,因为它更方便,更安全。
方法和属性注入在这里就是补充说明一下,因为它们的使用场景比较少,所以这里就不再深入讲解了。

示例代码:一个依赖 IdGenService 的后台定时任务

usingMicrosoft.EntityFrameworkCore;namespaceTestEFc_IEQable_AspNetCore01x02{publicclassTestHostedService:BackgroundService{privatereadonlyILogger<TestHostedService>_logger;privatereadonlyIIdGenService_idGenService;privateint_executionCount;publicTestHostedService(ILogger<TestHostedService>logger,IIdGenServiceidGenService){_logger=logger;_idGenService=idGenService;}protectedoverrideasyncTaskExecuteAsync(CancellationTokenstoppingToken){_logger.LogInformation("定时托管服务启动.");awaitDoWork();usingPeriodicTimertimer=new(TimeSpan.FromSeconds(10));try{while(awaittimer.WaitForNextTickAsync(stoppingToken)){awaitDoWork();}}catch(OperationCanceledException){_logger.LogInformation("定时托管服务已停止.");}}privateasyncTaskDoWork(){stringid=await_idGenService.NewGuidAsync(5);_logger.LogInformation($"生成Id:{id}");intcount=Interlocked.Increment(ref_executionCount);awaitTask.Delay(TimeSpan.FromSeconds(2));_logger.LogInformation("定时托管服务运行 {Count}",count);}}}

在Program.cs中注册服务

builder.Services.AddHostedService<TestHostedService>();//builder.Services.AddTransient<IIdGenService, IdGenService>();//builder.Services.AddScoped<IIdGenService, IdGenService>();//builder.Services.AddSingleton<IIdGenService, 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,由容器直接 new
Scoped必须在 Scope 内创建(Http Request)

那如果我们非要在 Singleton 中注入 Scoped 呢?

解决方案

使用 using 和 IServiceScopeFactory 来创建一个临时的 Scoped 作用域

usingMicrosoft.EntityFrameworkCore;namespaceTestEFc_IEQable_AspNetCore01x02{publicclassTestHostedService:BackgroundService{privatereadonlyILogger<TestHostedService>_logger;privatereadonlyIServiceScopeFactory_serviceScopeFactory;//看这里privateint_executionCount;publicTestHostedService(ILogger<TestHostedService>logger,IServiceScopeFactoryserviceScopeFactory){_logger=logger;_serviceScopeFactory=serviceScopeFactory;}protectedoverrideasyncTaskExecuteAsync(CancellationTokenstoppingToken){_logger.LogInformation("定时托管服务启动.");awaitDoWork();usingPeriodicTimertimer=new(TimeSpan.FromSeconds(10));try{while(awaittimer.WaitForNextTickAsync(stoppingToken)){awaitDoWork();}}catch(OperationCanceledException){_logger.LogInformation("定时托管服务已停止.");}}privateasyncTaskDoWork(){//看这里///<using和IServiceScopeFactory>usingvarscope=_serviceScopeFactory.CreateScope();vardateContext=scope.ServiceProvider.GetRequiredService<DateContext>();///<using和IServiceScopeFactory/>vartoDoList=awaitdateContext.ToDoListTable.Include(t=>t.TransactionNodes).FirstOrDefaultAsync(x=>x.Id=="1ca");_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}");}intcount=Interlocked.Increment(ref_executionCount);awaitTask.Delay(TimeSpan.FromSeconds(2));_logger.LogInformation("定时托管服务运行 {Count}",count);}}}

这样就解决了,运行截图:

http://www.jsqmd.com/news/489774/

相关文章:

  • 1.5 吨燃气蒸汽发生器 全自动运行 省气省心
  • 智能传感器:从概念解析到设计实践与未来展望
  • Day.js基本使用
  • 探索 COMSOL 顺层钻孔瓦斯抽采:双孔隙介质数值模拟模型
  • 绝大多数Wi-Fi 7路由器,根本无法实现真正的同时合并频段
  • 〘 8-1 〙软考高项 | 第15章:项目风险管理(上)
  • Js: 标识符、关键字、保留字和运算符
  • 华南产业集群赋能:2026广州汽车电子展,技术迭代与全球机遇共振?
  • 三大 AI 芯片架构
  • 微信 AI 小程序成长计划来了,我们怎么把混元接进了产品里
  • 网络共享-“引用的账户当前已锁定。且可能无法登陆”问题解决
  • 登录微信可以但无法访问浏览器
  • 样本冰箱之所以能实现自动报备温湿度数据是因为温湿度监控设备吗?
  • 桶排序原理与Python实现详解
  • PHP+CPU的生命周期的庖丁解牛
  • 压缩文件怎么设置密码?RAR三种加密方法步骤
  • 基于改进粒子群算法的微电网多目标优化调度探索
  • 信号分析仪 | 电子系统EMI故障诊断与精准测量
  • 【2026 最新】Spaceship Titanic Kaggle 入门实战:从数据清洗到 XGBoost 交叉验证
  • L2-036 网红点打卡攻略
  • 315后的职场“打假”:猎头行业的诚信底线与候选人的避坑指南
  • 第7章:Docker network网络管理(网络模式和创建docker网络)
  • AI + 技术文档:瑞萨AI技术助手构建
  • 深入解析USB传输:流程、规范与核心概念详解
  • AI写论文的秘密武器!4款AI论文生成工具助力期刊论文发表
  • 2026年3月14号,萨科微和金航标组织了开年的第一场篮球赛和羽毛球!
  • 2026年口碑好的海南落户咨询单位推荐,靠谱品牌全解析 - 工业品网
  • docker查找大日志并清除
  • PANASONIC松下 AXE530127 SMD 板对板与背板连接器
  • ConcurrentHashMap