You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

ASP.NET Core 8 Web API:单例消费作用域服务报错问题

问题根源

ASP.NET Core中,AddHostedService注册的后台服务默认是**Singleton(单例)**生命周期,而DI容器有严格的生命周期依赖规则:Singleton服务不能直接依赖Scoped服务——因为Scoped服务是单次请求/作用域内创建,生命周期远短于Singleton,直接注入会导致Scoped服务被Singleton长期持有,引发资源泄漏、DbContext上下文异常等问题。

你尝试将IVdcUnitOfWork改为Singleton后,又触发新错误,是因为IVdcUnitOfWork依赖的DbContextWrite(EF Core上下文默认是Scoped生命周期),同样违反了"Singleton不能依赖Scoped"的规则。

解决方案:使用IServiceScopeFactory创建作用域(推荐)

正确的做法是在后台服务中通过IServiceScopeFactory动态创建Scoped作用域,每次执行任务时从作用域中获取所需的Scoped服务,用完自动释放,既符合DI规则,又避免资源泄漏。

步骤1:修改后台服务代码

只在构造函数中注入IServiceScopeFactory和ILogger,在任务执行时动态创建作用域并解析服务:

public sealed class CheckDatabaseHealthBackgroundService : BackgroundService, IDisposable
{
    private readonly IServiceScopeFactory _scopeFactory;
    private readonly ILogger<CheckDatabaseHealthBackgroundService> _logger;
    
    public CheckDatabaseHealthBackgroundService(
        IServiceScopeFactory scopeFactory,
        ILogger<CheckDatabaseHealthBackgroundService> logger)
    {
        _scopeFactory = scopeFactory;
        _logger = logger;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            try
            {
                // 创建Scoped作用域,using会自动释放作用域及内部所有服务
                using var scope = _scopeFactory.CreateScope();
                var sp = scope.ServiceProvider;
                
                // 从作用域中解析所需的Scoped/Singleton服务
                var jobUtility = sp.GetRequiredService<IJobUtility>();
                var unitOfWork = sp.GetRequiredService<IVdcUnitOfWork>();
                var connFactory = sp.GetRequiredService<ICloudStackCoreConnectionFactory>();
                var cache = sp.GetRequiredService<IMemoryCache>();
                
                // 执行你的数据库健康检查逻辑
                await RunHealthCheck(jobUtility, unitOfWork, connFactory, cache, stoppingToken);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "数据库健康检查任务执行失败");
            }

            // 任务间隔,可根据业务调整
            await Task.Delay(TimeSpan.FromMinutes(5), stoppingToken);
        }
    }

    private async Task RunHealthCheck(IJobUtility jobUtility, IVdcUnitOfWork unitOfWork, ICloudStackCoreConnectionFactory connFactory, IMemoryCache cache, CancellationToken stoppingToken)
    {
        _logger.LogInformation("开始执行数据库健康检查");
        // 这里编写你的具体业务逻辑
    }
}

步骤2:恢复原有的服务注册配置

不需要修改任何服务的生命周期,保持Scoped注册即可(IMemoryCache是ASP.NET Core默认注册的Singleton,无需手动添加):

builder.Services.AddScoped<IHypervisorRepository, HypervisorRepository>();
builder.Services.AddScoped<IAccountRepository, AccountRepository>();
builder.Services.AddScoped<ICloudStackDbUnitOfWork, CloudStackDbUnitOfWork>();
builder.Services.AddScoped<ICloudStackCoreConnectionFactory, MySqlCloudStackCoreConnectionFactory>();

builder.Services.AddScoped<IJobUtility, JobUtility>();
builder.Services.AddScoped<IVdcUnitOfWork, VdcUnitOfWork>();

builder.Services.AddHostedService<CheckDatabaseHealthBackgroundService>();
为什么推荐这个方案?
  1. 严格遵循DI生命周期规则,避免资源泄漏和线程安全问题(EF Core的DbContext本身不是线程安全的,Scoped作用域能保证每次任务使用独立的DbContext实例)
  2. 无需修改现有服务的注册逻辑,降低代码改动成本
  3. 所有Scoped服务会在作用域释放时自动回收,符合ASP.NET Core的设计最佳实践

内容的提问来源于stack exchange,提问作者Diego Perez

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.21 13:37:08