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
相关产品推荐
相关产品推荐

