理解依赖注入的生命周期:Singleton、Scoped 和 Transient

发布: (2025年12月3日 GMT+8 18:55)
6 min read
原文: Dev.to

Source: Dev.to

Singleton

关键特性

  • 整个应用程序生命周期只有一个实例
  • 在所有请求之间共享
  • 第一次请求时创建一次
  • 应用程序关闭时释放

何时使用

  • 无状态服务(没有实例特定的数据)
  • 创建成本高的服务(缓存、HTTP 客户端)
  • 共享资源(配置、日志、缓存)
// Register as singleton
services.AddSingleton();
services.AddSingleton();

示例:缓存服务

public class CacheService : ICacheService
{
    private readonly Dictionary _cache = new();
    private readonly object _lock = new();

    public void Set(string key, T value)
    {
        lock (_lock) { _cache[key] = value; }
    }

    public T Get(string key)
    {
        lock (_lock)
        {
            return _cache.TryGetValue(key, out var value) ? (T)value : default;
        }
    }
}

注意事项

  • 共享可变状态
  • 线程安全问题(确保适当的同步)
  • 持有非托管资源时实现 IDisposable
// BAD: Singleton with mutable state
public class BadService
{
    public string CurrentUserId { get; set; } // Shared across all requests!
}

Scoped

关键特性

  • 每个作用域(通常是每个 HTTP 请求)一个实例
  • 在同一作用域内共享,不同作用域之间不共享
  • 作用域开始时创建
  • 作用域结束时释放

何时使用

  • 数据库上下文(例如 Entity Framework DbContext
  • 需要在请求期间保持状态的请求特定服务
  • 工作单元(Unit‑of‑Work)模式
// Register as scoped
services.AddScoped();
services.AddScoped();

示例:数据库上下文和订单服务

public class ApplicationDbContext : DbContext
{
    public DbSet Orders { get; set; }
}

public class OrderService
{
    private readonly ApplicationDbContext _context;

    public OrderService(ApplicationDbContext context) => _context = context;

    public async Task CreateOrder(Order order)
    {
        _context.Orders.Add(order);
        await _context.SaveChangesAsync();
        return order;
    }
}

注意事项

  • 将 scoped 服务注入到 singleton 中(会导致生命周期不匹配)
// BAD: Singleton depending on scoped service
public class BadSingleton
{
    private readonly IScopedService _scoped; // Error!
}

// GOOD: Resolve scoped service via IServiceProvider
public class GoodSingleton
{
    private readonly IServiceProvider _serviceProvider;

    public GoodSingleton(IServiceProvider serviceProvider) => _serviceProvider = serviceProvider;

    public void DoWork()
    {
        using var scope = _serviceProvider.CreateScope();
        var scoped = scope.ServiceProvider.GetRequiredService();
        scoped.DoWork();
    }
}
  • 不要在后台任务中捕获 scoped 服务,除非创建了适当的作用域。

Transient

关键特性

  • 每次请求都会创建新实例
  • 消费者之间不共享
  • 按需创建,超出作用域后由垃圾回收释放
  • 三种方式中生命周期最短

何时使用

  • 轻量级、创建成本低的服务
  • 无状态操作
  • 必须不共享的服务(共享会导致错误)
// Register as transient
services.AddTransient();
services.AddTransient, OrderValidator>();

示例:订单验证器

public class OrderValidator : IValidator
{
    public ValidationResult Validate(Order order)
    {
        var result = new ValidationResult();

        if (order.Items == null || order.Items.Count == 0)
            result.AddError("Order must have at least one item");

        return result;
    }
}

注意事项

  • 将成本高的服务注册为 transient 会影响性能
  • 将 transient 实例保存在静态字段中会导致内存泄漏
  • 当 singleton 已足够时,避免不必要的创建

对比表

方面SingletonScopedTransient
实例数量整个应用只有一个每个作用域一个每次请求都会新建
生命周期应用程序生命周期作用域生命周期(每个请求)最短——作用域结束后即被回收
内存使用低(共享)中等高(实例很多)
线程安全必须是线程安全的通常不需要通常不需要
释放时机应用关闭时作用域结束时垃圾回收时
典型使用场景无状态、创建成本高的服务请求特定服务轻量级、无状态服务

服务注册示例

public void ConfigureServices(IServiceCollection services)
{
    // Singleton: Shared across all requests
    services.AddSingleton();

    // Scoped: One per HTTP request
    services.AddScoped();

    // Transient: New instance each time
    services.AddTransient();
}

后台工作者示例(在托管服务中使用 Scoped 服务)

public class BackgroundWorker : BackgroundService
{
    private readonly IServiceProvider _serviceProvider;

    public BackgroundWorker(IServiceProvider serviceProvider) =>
        _serviceProvider = serviceProvider;

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            // Create a scope for each iteration
            using var scope = _serviceProvider.CreateScope();
            var scopedService = scope.ServiceProvider.GetRequiredService();
            await scopedService.DoWorkAsync();

            await Task.Delay(1000, stoppingToken);
        }
    }
}

生命周期依赖规则

  • 服务只能依赖 相同或更长 生命周期的服务。
服务生命周期允许的依赖
SingletonSingleton
ScopedSingleton、Scoped
TransientSingleton、Scoped、Transient

常见错误

// BAD: Singleton depending on scoped service
public class SingletonService
{
    private readonly IScopedService _scoped; // Error!
}

// GOOD: Resolve scoped service via IServiceProvider when needed
public class SingletonService
{
    private readonly IServiceProvider _serviceProvider;

    public SingletonService(IServiceProvider serviceProvider) =>
        _serviceProvider = serviceProvider;

    public void DoWork()
    {
        using var scope = _serviceProvider.CreateScope();
        var scoped = scope.ServiceProvider.GetRequiredService();
        // Use scoped service...
    }
}
Back to Blog

相关文章

阅读更多 »