服务生命周期:Transient、Scoped、Singleton
了解 Transient、Scoped 和 Singleton 生命周期如何影响对象的创建、共享与释放。
服务生命周期:Transient、Scoped、Singleton 是 CoddyKit 上的免费 C# Academy 课时。 这是第 2 节课,共 4 节。 你可以在下方免费阅读本课时的完整内容 — 然后在浏览器中使用内置代码编辑器和全天候 AI 导师进行实践。 这是 C# Academy 学习路径的一部分,你的进度在网页和 CoddyKit 应用中同步。 C# Academy 课程共包含 4 节课。
生命周期为何重要
服务生命周期决定实例存活多长时间以及创建多少个实例。选择错误的生命周期会导致隐蔽的错误,例如请求之间的数据泄露或对象创建过多。
瞬时:每次创建新的实例
瞬时服务每次从容器请求时都会重新创建。对于轻量级、无状态的服务,可以使用这种生命周期;在这类服务中共享状态可能会带来风险。
builder.Services.AddTransient<IEmailSender, SmtpEmailSender>();
// Each resolution creates a new instance:
// var a = sp.GetRequiredService<IEmailSender>(); // new
// var b = sp.GetRequiredService<IEmailSender>(); // new (different)作用域:每个请求一次
作用域服务在每个作用域内创建一次——在 ASP.NET Core 中通常对应一个 HTTP 请求。同一个实例会在一次请求中共享,但下一个请求会创建新的实例。
builder.Services.AddScoped<AppDbContext>();
// Within a single HTTP request:
// Both resolve to the SAME AppDbContext instance
// -> consistent unit of work across the request单例:贯穿应用程序生命周期的一个实例
单例服务只创建一次,并在整个应用程序生命周期内重复使用。对于创建成本高且线程安全的服务(例如配置缓存),可以使用这种生命周期。
builder.Services.AddSingleton<IMemoryCache, MemoryCache>();
// Or register a pre-built instance:
var config = new AppConfig { MaxRetries = 3 };
builder.Services.AddSingleton<IAppConfig>(config);三种生命周期对比
下面进行快速并列比较:
- 瞬时——每次请求创建新实例;适用于无状态工具
- 作用域——每个 HTTP 请求一个实例;适用于 DbContext、工作单元
- 单例——整个应用程序生命周期内一个实例;适用于缓存、配置
被捕获依赖项问题
将作用域或瞬时服务注入单例是常见错误。单例会持有对短生命周期服务的引用,使其存活时间过长。
// BAD: Singleton captures a Scoped service
public class MySingleton
{
private readonly AppDbContext _db; // Scoped!
public MySingleton(AppDbContext db) => _db = db;
// _db is now stuck alive for the app's lifetime
}检测作用域违规
启用 ValidateScopes,这样当检测到被捕获的依赖项时,容器会在启动时抛出错误。这可以在错误导致生产环境数据问题之前将其发现。
builder.Host.UseDefaultServiceProvider(options =>
{
options.ValidateScopes = true; // detect captive deps
options.ValidateOnBuild = true; // fail fast at start
});请求之外的作用域服务
要在后台任务中解析作用域服务,请使用 IServiceScopeFactory 创建显式作用域。切勿直接从根容器解析作用域服务。
public class MyWorker : BackgroundService
{
private readonly IServiceScopeFactory _factory;
public MyWorker(IServiceScopeFactory factory) => _factory = factory;
protected override async Task ExecuteAsync(CancellationToken ct)
{
using var scope = _factory.CreateScope();
var db = scope.ServiceProvider.GetRequiredService<AppDbContext>();
await db.Users.ToListAsync(ct);
}
}瞬时可释放服务
从根容器解析的瞬时 IDisposable 服务会一直被跟踪,直到容器释放(应用程序关闭)。请在作用域内解析它们,以便及时释放资源。
// Correctly scoped transient disposable
using var scope = sp.CreateScope();
var svc = scope.ServiceProvider.GetRequiredService<MyDisposableService>();
await svc.DoWorkAsync();
// svc.Dispose() called when scope is disposed选择合适的生命周期
一个简单的判断指南:
- 服务是否无状态?→ 瞬时
- 服务是否依赖请求(例如用户身份、DbContext)?→ 作用域
- 服务是否线程安全且创建成本高?→ 单例
实际应用:同时使用三种生命周期
典型的 Web API 会为不同的服务注册合适的生命周期,以确保正确性和性能。
builder.Services.AddSingleton<IConfiguration>(builder.Configuration);
builder.Services.AddSingleton<ICacheService, RedisCacheService>();
builder.Services.AddScoped<AppDbContext>();
builder.Services.AddScoped<IOrderRepository, OrderRepository>();
builder.Services.AddScoped<OrderService>();
builder.Services.AddTransient<IEmailSender, SendGridEmailSender>();快速检查
当单例服务持有对作用域服务的引用时,会出现什么问题?
回顾:服务生命周期
要点总结:
- 瞬时:每次创建新实例——适用于无状态、轻量级服务
- 作用域:每个请求一个实例——适用于 DbContext、存储库、工作单元
- 单例:始终只有一个实例——适用于缓存、配置、线程安全工具
- 切勿将短生命周期服务注入较长生命周期的服务(被捕获的依赖项)
- 使用
IServiceScopeFactory在后台任务中解析作用域服务
常见问题解答
「服务生命周期:Transient、Scoped、Singleton」课时是免费的吗?
是的 — 「服务生命周期:Transient、Scoped、Singleton」的完整文本可在网页上免费阅读。要进行交互式练习(内置代码编辑器和全天候 AI 导师)并解锁 C# Academy 课程的其余内容,请升级到 CoddyKit PRO。 C# Academy 课程共包含 4 节课。
「服务生命周期:Transient、Scoped、Singleton」这节课中我会学到什么?
了解 Transient、Scoped 和 Singleton 生命周期如何影响对象的创建、共享与释放。 你通过在浏览器中直接运行的动手代码来练习 C# Academy,全天候 AI 导师会在你学习这节课的过程中回答你的问题。
学习 C# Academy 需要有经验吗?
无需任何先前经验。CoddyKit 上的 C# Academy 课程适合初学者到高级学习者,你可以从这里开始或从头开始,按照自己的节奏学习。 这是第 2 节课,共 4 节。
「服务生命周期:Transient、Scoped、Singleton」课时需要多长时间?
大多数 CoddyKit 课程大约需要 5–10 分钟。每节课都很精短且互动,所以你能稳步进步,并在网页和应用中从离开的地方继续。
我能在这节 C# Academy 课中编写并运行代码吗?
能。每节 C# Academy 课都包含内置代码编辑器,你可以在浏览器中直接编写并运行真实代码,并获得即时 AI 反馈 — 无需本地设置。