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

Worker Service依赖注入错误:单例IHostedService无法消费Scoped服务

问题根因

.NET 依赖注入容器有严格的生命周期校验规则:单例(Singleton)生命周期的服务,不允许直接注入并消费作用域(Scoped)生命周期的服务。
你代码中通过AddHostedService<Worker>()注册的后台服务默认是单例生命周期,而IServiceManager、IRepoManager、AppDbContext都注册为Scoped生命周期,直接在Worker构造函数注入Scoped服务,就会触发你看到的报错。

解决方案

不要在单例的Worker构造函数中直接注入Scoped服务,通过IServiceScopeFactory手动创建作用域解析Scoped服务即可,同时修正现有代码中手动new服务绕过DI容器的隐患。

1. 修改Worker类实现

移除构造函数中对Scoped服务的直接注入,改为注入IServiceScopeFactory,在每次执行任务时创建独立作用域解析服务,用完自动释放:

public class Worker : BackgroundService
{
    private readonly ILogger<Worker> _logger; 
    private readonly IServiceScopeFactory _scopeFactory;

    public Worker(ILogger<Worker> logger, IServiceScopeFactory scopeFactory)
    {
        _logger = logger; 
        _scopeFactory = scopeFactory;
    } 
    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        while (!stoppingToken.IsCancellationRequested)
        {
            // 创建独立的Scoped作用域
            using var scope = _scopeFactory.CreateScope();
            // 从作用域中解析需要的服务
            var serviceManager = scope.ServiceProvider.GetRequiredService<IServiceManager>();
            
            var result = serviceManager.CountryService.GetAll(); 
            _logger.LogInformation("Worker running at: {time}", DateTimeOffset.Now);
            await Task.Delay(1000, stoppingToken);
            // using代码块结束后,作用域及其中解析的DbContext等Scoped服务会被自动回收释放
        }
    }
}

2. 修正服务实现,移除手动new的写法

你当前ServiceManager、RepoManager中手动实例化业务服务、仓储的写法完全绕过了DI容器,后续新增依赖时需要手动逐层传参,维护性极差,改为构造函数注入的标准写法:

修改ServiceManager

public class ServiceManager : IServiceManager
{
    public ICountryService CountryService { get; }

    public ServiceManager(ICountryService countryService)
    {
        CountryService = countryService; 
    } 
}

修改RepoManager

public class RepoManager : IRepoManager
{
    public ICountryRepo CountryRepo { get; }

    public RepoManager(ICountryRepo countryRepo)
    {
        CountryRepo = countryRepo; 
    } 
}

3. 补全Program.cs中的服务注册

你原有代码没有注册ICountryService、ICountryRepo,在ConfigureServices方法中补充注册即可:

.ConfigureServices((builder, services) =>
{
    services.AddHostedService<Worker>(); 
    services.AddDbContext<AppDbContext>(options =>
        options.UseSqlServer(builder.Configuration.GetConnectionString("DefaultConnection"))
    );

    services.AddIdentity<IdentityUser, IdentityRole>().AddDefaultTokenProviders()
        .AddEntityFrameworkStores<AppDbContext>(); 
     
    services.AddScoped<IServiceManager, ServiceManager>();
    services.AddScoped<IRepoManager, RepoManager>();  
    // 补全下面两个注册
    services.AddScoped<ICountryService, CountryService>();
    services.AddScoped<ICountryRepo, CountryRepo>();
})
避坑提醒
  • 不要为了绕过报错直接把IServiceManager、IRepoManager、AppDbContext生命周期改为Singleton。EF Core的DbContext是按Scoped生命周期设计的,单例化会导致数据脏读、数据库连接泄漏、并发操作异常等严重问题。
  • 不要在单例服务中长期持有从作用域解析出来的Scoped服务引用,否则Scoped服务会被单例间接锚定,无法被释放,等价于变成单例生命周期,同样会引发上述问题。
  • 每次执行定时任务都创建新的作用域,用完即释放,符合DbContext短生命周期、轻量实例化的官方设计建议。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.09.01 04:51:23