文章目录
一、Transient(瞬时生命周期)
原理
使用方式
// 注册服务
builder.Services.AddTransient<IMyService, MyService>();
// 使用示例
public class ClientService
{
private readonly IMyService _service1;
private readonly IMyService _service2;
public ClientService(IMyService service1, IMyService service2)
{
// 两个参数会收到不同的实例
_service1 = service1;
_service2 = service2;
}
}
核心特性
- 每次请求创建新实例
- 不共享状态
- 自动释放(当请求处理完成时)
适用场景
- 轻量级无状态服务(如计算器、验证器)
- 需要线程隔离的服务
- 每次操作需要全新状态的场景
// 典型应用:数据转换服务
public interface IDataTransformer
{
string Transform(string input);
}
public class ReverseTransformer : IDataTransformer
{
public string Transform(string input)
=> new string(input.Reverse().ToArray());
}
// 注册
services.AddTransient<IDataTransformer, ReverseTransformer>();
优势
- 内存安全:不会意外共享状态
- 线程安全:每个线程使用独立实例
- 简单可靠:无需考虑状态管理
劣势
- 性能开销:频繁创建/销毁对象
- 内存碎片:大量短期对象增加GC压力
- 资源浪费:不适合初始化成本高的服务
二、Scoped(作用域生命周期)
原理
使用方式
// 注册服务
builder.Services.AddScoped<IUserRepository, UserRepository>();
// ASP.NET Core 中间件中
app.Use(async (context, next) =>
{
// 手动创建作用域
using var scope = context.RequestServices.CreateScope();
var repo = scope.ServiceProvider.GetService<IUserRepository>();
await repo.LogRequestAsync(context.Request);
await next();
});
核心特性
- 作用域内单例(同一作用域内实例共享)
- 跨请求隔离(不同请求不同实例)
- 自动释放(作用域结束时)
适用场景
- 数据库上下文(如EF Core DbContext)
- 请求级状态管理
- 事务处理单元
// 典型应用:EF Core DbContext
public class AppDbContext : DbContext
{
public DbSet<User> Users { get; set; }
}
// 注册
services.AddScoped<AppDbContext>();
// 在控制器中使用
public class UserController : Controller
{
private readonly AppDbContext _context;
public UserController(AppDbContext context)
{
_context = context; // 同一请求内共享实例
}
}
优势
- 状态隔离:不同请求互不影响
- 资源优化:重用初始化成本高的对象
- 事务一致性:天然支持事务边界(整个请求)
劣势
- 作用域泄漏:意外在单例中引用会导致内存泄漏
// 错误示例:单例中引用Scoped服务
public class SingletonService
{
private readonly IUserRepository _repo; // 危险!
public SingletonService(IUserRepository repo)
{
_repo = repo; // 这会导致Scoped服务变成"伪单例"
}
}
- 异步风险:在async/await中可能跨越不同作用域
- 测试复杂性:需模拟作用域环境
三、Singleton(单例生命周期)
原理
使用方式
// 注册服务
builder.Services.AddSingleton<ICacheService, CacheService>();
// 预创建实例(立即初始化)
var cache = new CacheService();
builder.Services.AddSingleton<ICacheService>(cache);
// 延迟初始化
builder.Services.AddSingleton<IBackgroundService>(provider =>
new BackgroundService(provider.GetRequiredService<ILogger>()));
核心特性
- 全局唯一实例(整个应用生命周期)
- 首次请求时创建(除非预注册实例)
- 应用关闭时释放
适用场景
- 配置服务(如IOptions)
- 内存缓存
- 共享资源连接(如Redis连接池)
// 典型应用:内存缓存
public class MemoryCacheService : ICacheService, IDisposable
{
private readonly ConcurrentDictionary<string, object> _cache = new();
private Timer _cleanupTimer;
public MemoryCacheService()
{
_cleanupTimer = new Timer(_ => Cleanup(), null, 0, 60_000);
}
public object Get(string key) => _cache.TryGetValue(key, out var value) ? value : null;
public void Dispose() => _cleanupTimer?.Dispose();
}
// 注册
services.AddSingleton<ICacheService, MemoryCacheService>();
优势
- 性能最佳:单次初始化,零实例化开销
- 全局状态共享:跨请求共享数据
- 资源集中管理:如连接池、线程池
劣势
- 线程安全风险:需手动实现同步机制
public class CounterService
{
private int _count = 0;
// 危险:非线程安全
public void Increment() => _count++;
// 正确:线程安全版本
public void SafeIncrement() => Interlocked.Increment(ref _count);
}
- 内存泄漏:意外持有引用导致GC无法回收
- 启动延迟:复杂单例初始化影响应用启动时间
三、生命周期对比分析
功能对比表
特性 | Transient | Scoped | Singleton |
---|---|---|---|
实例创建时机 | 每次请求 | 作用域首次请求 | 全局首次请求 |
实例数量 | 多个 | 每作用域一个 | 全局一个 |
状态共享范围 | 无共享 | 作用域内共享 | 全局共享 |
线程安全要求 | 低 | 中等 | 高 |
适用场景 | 无状态服务 | 请求级状态 | 全局共享资源 |
内存管理 | 自动回收 | 作用域结束时回收 | 应用结束时回收 |
性能开销 | 高(频繁创建) | 中等 | 低(单次创建) |
性能基准测试
BenchmarkDotNet=v0.13.1, OS=Windows 10
Intel Core i7-11800H 2.30GHz, 1 CPU, 16 cores
| 方法 | 调用次数 | 平均耗时 | 内存分配 |
|---------------------|---------|----------|----------|
| TransientResolve | 10000 | 158 ns | 32 B |
| ScopedResolve | 10000 | 76 ns | 0 B |
| SingletonResolve | 10000 | 38 ns | 0 B |
典型错误案例
案例1:作用域泄漏
// 错误:单例中注入Scoped服务
builder.Services.AddSingleton<ReportService>();
builder.Services.AddScoped<DatabaseContext>();
// 解决方案1:使用工厂方法
builder.Services.AddSingleton<ReportService>(provider =>
new ReportService(provider.GetRequiredService<DatabaseContext>));
// 解决方案2:改为作用域服务
builder.Services.AddScoped<ReportService>();
案例2:线程竞争
public class CacheService
{
private Dictionary<string, object> _cache = new();
// 错误:非线程安全
public void Add(string key, object value)
{
_cache[key] = value;
}
// 正确:使用并发集合
private ConcurrentDictionary<string, object> _safeCache = new();
public void SafeAdd(string key, object value)
{
_safeCache[key] = value;
}
}
案例3:资源未释放
public class FileService : IDisposable
{
private FileStream _fileStream;
public FileService()
{
_fileStream = File.Open("data.bin", FileMode.Open);
}
// 必须实现Dispose
public void Dispose()
{
_fileStream?.Dispose();
}
}
// 注册(Singleton需显式释放)
builder.Services.AddSingleton<FileService>();
四、生命周期决策树
graph TD
A[新服务注册] --> B{是否有状态?}
B -->|无状态| C[优先Transient]
B -->|有状态| D{状态共享范围?}
D -->|请求级| E[选择Scoped]
D -->|应用级| F{是否线程安全?}
F -->|是| G[选择Singleton]
F -->|否| H[重构为线程安全或选Scoped]
C --> I{创建成本高?}
I -->|是| J[考虑Scoped]
I -->|否| K[保持Transient]
G --> L{需要立即初始化?}
L -->|是| M[预注册实例]
L -->|否| N[延迟初始化]
E --> O[确保作用域边界]
G --> P[实现IDisposable]
五、最佳实践指南
默认选择Transient
- 除非有明确需求,否则优先无状态服务
// 好:无状态服务使用Transient services.AddTransient<IValidator, EmailValidator>();
Scoped生命周期黄金法则
- 一个请求对应一个工作单元
services.AddScoped<OrderProcessingService>();
Singleton安全准则
- 实现线程安全
- 实现IDisposable
- 避免依赖非Singleton服务
public class SafeCache : ICache, IDisposable { private readonly ConcurrentDictionary<string, object> _store; private readonly Timer _timer; private readonly ReaderWriterLockSlim _lock = new(); public void Dispose() { _timer?.Dispose(); _lock?.Dispose(); } }
生命周期验证
// 启用容器验证 var provider = services.BuildServiceProvider(validateScopes: true);
混合生命周期策略
public class HybridService { // 长周期依赖Singleton private readonly ICache _cache; // 短周期依赖Transient工厂 private readonly Func<ITransientService> _factory; public HybridService( ICache cache, Func<ITransientService> factory) { _cache = cache; _factory = factory; } public void Process() { // 按需创建Transient实例 using var service = _factory(); service.DoWork(_cache.GetData()); } }
六、总结
- Transient:轻量级无状态服务的首选,但需警惕高频创建的性能开销
- Scoped:请求敏感资源(如数据库连接)的黄金标准,注意作用域边界
- Singleton:全局共享资源的最佳载体,但必须确保线程安全和资源释放
架构师建议:在大型系统中采用分层生命周期策略:
- 基础设施层(缓存、配置):Singleton
- 领域服务层:Scoped
- 工具类/辅助服务:Transient
定期使用
.BuildServiceProvider(validateScopes: true)
检测生命周期错误,
这对预防生产环境的内存泄漏和状态污染至关重要。