- 文档
- 教程
- 知识库
【免费下载链接】developer-roadmap
Interactive roadmaps, guides and other educational content to help developers grow in their careers.
依赖注入(Dependency Injection)生命周期决定了服务容器何时创建、何时复用、何时销毁服务实例,是 ASP.NET Core 应用架构设计中最关键的基础决策之一。本文以 developer-roadmap 仓库中 ASP.NET Core 路线的 life-cycles 主题为核心,系统讲解 Transient、Scoped、Singleton 三种生命周期的行为差异、注册方法、选择原则与常见陷阱,帮助读者正确设计服务注册策略,避免内存泄漏、状态错乱与性能劣化。
什么是依赖注入生命周期
在 ASP.NET Core 中,Dependency Injection 是一种软件设计模式,允许类从外部源接收其依赖项,而不是在内部自行创建它们。该模式被直接构建到框架中,用于管理整个应用程序中服务的生命周期和实例化(参见 dependency-injection)。
而**生命周期(Life Cycle)**则进一步回答了三个问题:
- 服务实例在什么时刻被创建?
- 同一个实例会在多少范围内被共享?
- 实例在什么时机被释放(Dispose)?
在 ASP.NET Core 基础 中,Dependency Injection 被列为框架的核心基础能力之一。绝大多数内置框架功能(EF Core、ASP.NET Core Identity、日志、配置等)都通过内置容器以某种生命周期注册并解析,因此理解生命周期是深入 ASP.NET Core 的前提。
ASP.NET Core 内置了三种主要的服务生命周期(Lifetime):
| 生命周期 | 实例创建时机 | 共享范围 | 典型用途 |
|---|---|---|---|
| Transient | 每次请求服务时都创建新实例 | 不共享 | 轻量、无状态的服务 |
| Scoped | 每个客户端请求(HTTP 请求)内创建一次 | 同一次请求内共享 | 与请求绑定状态的服务 |
| Singleton | 首次被请求时或应用启动时创建 | 整个应用生命周期共享 | 共享状态、配置、缓存 |
Transient:每次请求都全新创建
Transient(瞬态)服务每次从服务容器请求时都会创建。这是三种生命周期中最“短命”的一种。
适用场景:轻量、无状态(Stateless)的服务。由于每次注入都会产生一个全新实例,不会在不同组件之间共享,因此天然避免了共享状态带来的并发问题(参见 transient)。
builder.Services.AddTransient<IMyService, MyService>();行为要点:
- 同一个请求内,即使多个控制器或服务都注入
IMyService,也会得到不同的实例; - 每次解析都会执行构造函数,适用于构造成本低、不持有可变状态的服务;
- 实例由容器在所属作用域结束时统一释放。
需要留意的是:Transient 并非“免费”。如果一个 Transient 服务的构造开销较大(如建立数据库连接、加载大对象),高频创建会带来明显的性能损耗,此时应考虑 Scoped 或 Singleton。
Scoped:每次请求一个实例
Scoped(作用域)服务在每个客户端请求(HTTP 请求)内只创建一次。当以 Scoped 生命周期注册服务时,框架会为每一个独立的 HTTP 请求生成一个新实例,并让处理该请求的所有组件共享同一个实例(参见 scoped)。
builder.Services.AddScoped<IMyService, MyService>();行为要点:
- 一次请求内的 Controller、Service、Repository 等组件注入的
IMyService是同一个实例; - 数据在单个用户交互的完整生命周期内保持一致;
- 服务不会跨不同且无关的请求持久存在,避免请求间状态串扰。
典型应用:EF Core 的DbContext默认按 Scoped 注册,使得一次 HTTP 请求中的所有数据访问共享同一个上下文,从而支持事务、变更追踪(Change Tracker)的一致性。这是选择 Scoped 最经典的实际案例。
Singleton:整个应用共享一个实例
Singleton(单例)服务在首次被请求时(或在应用启动时)创建一次,此后在应用程序的整个生命周期内,所有后续请求都复用这同一个实例(参见 singleton)。
builder.Services.AddSingleton<IMyService, MyService>();行为要点:
- 全应用只有一份实例,所有请求、所有线程共享;
- 实例在应用启动/首次解析时创建,随应用进程结束而销毁;
- 适合管理共享状态、配置设置或缓存服务——需要跨系统不同部分维护数据的场景。
典型应用:内存缓存(IMemoryCache)、配置对象、日志提供器等跨请求共享的基础设施服务。
如何选择正确的生命周期
选择生命周期的核心判断依据是服务内部状态的性质与共享需求:
| 服务特征 | 推荐生命周期 |
|---|---|
| 轻量、无状态、构造便宜 | Transient |
| 有状态,且状态需在一次请求内保持一致 | Scoped |
| 有状态,且状态需全局共享(缓存、配置) | Singleton |
| 线程安全、可安全并发访问 | Singleton(需谨慎) |
| 依赖数据库上下文、事务边界 | Scoped(与DbContext对齐) |
常见陷阱与最佳实践
1. 捕获依赖(Captive Dependency):Singleton 依赖 Scoped
Singleton 实例在其整个生命周期内只能解析 Singleton 依赖。如果 Singleton 直接注入了 Scoped 服务,那么该 Scoped 服务实际上被“囚禁”为 Singleton 语义——它只会在 Singleton 创建时解析一次,并随 Singleton 存活,导致请求级状态泄漏与错误共享。
解决方案:改用 Scoped 注册上层服务,或通过IServiceScopeFactory.CreateScope()显式创建作用域后再解析。
2. 释放(Disposal)机制
- Transient 与 Scoped 实例由容器在所属作用域结束时释放(请求结束即触发);
- Singleton 实例在应用关闭时由容器释放;
- 若服务实现了
IDisposable,容器会负责调用Dispose(),但如果开发者在容器之外手动new出实例,则需自行管理释放。
3. Singleton 的线程安全
Singleton 实例会被多个请求的多个线程并发访问,内部可变状态必须保证线程安全(加锁、使用ConcurrentDictionary、不可变对象等),否则会产生难以排查的竞态问题。这也是为何无状态服务更推荐 Transient 的原因。
4. 默认值并非万能
ASP.NET Core 内置容器的默认注册(如DbContext为 Scoped、日志/缓存为 Singleton)适用于大多数场景,但在后台任务、消息消费者、定时作业等无 HTTP 请求上下文的环境中,Scoped 语义不再自动成立,需要手动创建作用域(IServiceScopeFactory)来获得与请求等价的边界。
生命周期背后的 DI 容器
生命周期由DI 容器(DI Container)负责落地执行。DI 容器是管理对象实例化与生命周期的框架组件:它是一个中央注册表,开发者在其中定义“哪个接口对应哪个实现”;当应用请求服务时,容器自动解析依赖、将实例注入类中,并依据定义的服务生命周期管理其释放(参见 di-containers)。
ASP.NET Core 默认使用Microsoft.Extensions.DependencyInjection——这是 .NET 提供的内置依赖注入库,作为一个轻量级容器管理对象的创建与生命周期。开发者可以在集中位置注册服务,之后这些服务通过构造函数自动注入到需要它们的类中(参见 microsoftextensions)。
注册 API 与生命周期一一对应:
// Program.cs —— 最小 API / 通用宿主 var builder = WebApplication.CreateBuilder(args); // Transient:每次请求都新建 builder.Services.AddTransient<ITransientService, TransientService>(); // Scoped:每个 HTTP 请求一个实例 builder.Services.AddScoped<IScopedService, ScopedService>(); // Singleton:整个应用共享一个实例 builder.Services.AddSingleton<ISingletonService, SingletonService>(); var app = builder.Build(); app.Run();当多个组件通过构造函数注入同一个注册时,具体拿到几个实例完全由生命周期决定:
public class WeatherController : ControllerBase { private readonly ITransientService _t1; private readonly ITransientService _t2; private readonly IScopedService _s1; private readonly ISingletonService _g1; public WeatherController( ITransientService t1, ITransientService t2, IScopedService s1, ISingletonService g1) { _t1 = t1; _t2 = t2; _s1 = s1; _g1 = g1; } }上述代码中:_t1 != _t2(每次注入都是新实例);_s1与同一请求内其他组件注入的 Scoped 实例相同;_g1与所有请求、所有组件共享同一个实例。
小结
依赖注入生命周期是 ASP.NET Core 应用架构的基石之一:
- Transient适合轻量无状态服务,避免共享状态问题,但需注意高频创建的成本;
- Scoped与 HTTP 请求边界对齐,保证单次交互内数据一致,是
DbContext等有请求态服务的首选; - Singleton提供应用级共享实例,适合缓存、配置等基础设施,但必须保证线程安全。
理解“何时创建、何时共享、何时销毁”这三个维度,并在注册服务时结合状态特征做出选择,就能构建出既高效又不易出错的 ASP.NET Core 应用。更进一步,可阅读仓库中 dependency-injection、di-containers 与 microsoftextensions 等关联主题,串联起完整的 DI 知识体系。
- 文档
- 教程
- 知识库
【免费下载链接】developer-roadmap
Interactive roadmaps, guides and other educational content to help developers grow in their careers.
相关推荐
CANN/asc-devkit Tensor API Copy与Matmul示例
Copy API and Matmul with Bias Example Based on Tensor API Overview This example
人工智能深度学习算子库CANNAscendASP.NET Core 依赖注入(Dependency Injection)完全指南:原理、生命周期与实战配置
ASP.NET Core 依赖注入(Dependency Injection)完全指南:原理、生命周期与实战配置 依赖注入(Dependency Injecti
文档教程知识库Radzen Blazor依赖注入:服务注册与组件生命周期详解
Radzen Blazor依赖注入:服务注册与组件生命周期详解 Radzen Blazor作为一套包含70+原生Blazor UI组件的强大框架,其依赖注入系统
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考