.NET Core如何用同一类带不同参数注册多个HostedService
.NET Core托管服务多实例注册问题解决
问题描述
需要将MyBackgroundService类注册为3个独立的HostedService实例,每个实例传入1-3的不同整数参数,但实际运行时断点仅触发一次,无法实现多实例并行运行。
问题根源
- 闭包变量捕获问题:循环中的变量
i被闭包捕获的是引用而非值,导致三个实例最终拿到的都是循环结束后的i=3,逻辑上无法区分实例,看起来像只运行了一次。 - 作用域管理不当:在服务注册的工厂方法中创建作用域并持有,会导致Scoped生命周期的服务(如
MyDBContext)被长期持有,违背Scoped服务的设计意图,同时可能引发资源占用问题。 - 同步阻塞问题:
ExecuteAsync方法中没有异步等待逻辑,会同步占用线程,导致托管服务运行异常。
解决方案及修改代码
1. 修复服务注册逻辑
修改循环内的变量捕获方式,并调整作用域创建时机:
var builder = Host.CreateDefaultBuilder(args); string env = Environment.GetEnvironmentVariable("ASPNETCORE_ENVIRONMENT") ?? "Production"; string version = typeof(Program).Assembly.GetName().Version.ToString(); var config = new ConfigurationBuilder() .AddJsonFile($"appsettings.{env}.json", optional: false, reloadOnChange: true).Build(); var v = config.GetConnectionString("yshDb"); String logpath = Path.Join(Path.GetDirectoryName(Process.GetCurrentProcess().MainModule.FileName), "TestServices.csv"); var csvLoggerProvider = new CsvLoggerProvider<MyBackgroundService>(logpath); IHostBuilder hostBuilder = Host.CreateDefaultBuilder(args); hostBuilder.ConfigureServices((hostContext, services) => { services.AddSingleton<ILoggerProvider>(csvLoggerProvider); services.AddDbContext<MyDBContext>(options => options.UseSqlServer(hostContext.Configuration.GetConnectionString("connStr")), ServiceLifetime.Scoped); services.AddScoped<IDocumentMoveData, DocumentMoveData>(); // 修复闭包变量问题:每次循环捕获当前i的值 for (int i = 1; i <= 3; ++i) { int currentInstanceNumber = i; services.AddHostedService<MyBackgroundService>(s => { // 这里只注入IServiceProvider,作用域在ExecuteAsync内部创建 return new MyBackgroundService( s.GetRequiredService<IServiceProvider>(), currentInstanceNumber, s.GetRequiredService<ILogger<MyBackgroundService>>()); }); } }); IHost host = hostBuilder.Build(); await host.StartAsync(); Console.ReadLine();
2. 修改MyBackgroundService实现
调整构造函数注入内容,在ExecuteAsync内部创建作用域并使用Scoped服务:
public class MyBackgroundService : BackgroundService { private readonly ILogger<MyBackgroundService> _logger; private readonly IServiceProvider _serviceProvider; private readonly int _instanceNumber; // 注入IServiceProvider,用于后续创建作用域 public MyBackgroundService(IServiceProvider serviceProvider, int instanceNumber, ILogger<MyBackgroundService> logger) { _serviceProvider = serviceProvider; _instanceNumber = instanceNumber; _logger = logger; } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { _logger.LogInformation("Background service instance {InstanceNumber} started", _instanceNumber); while (!stoppingToken.IsCancellationRequested) { // 每次循环创建独立作用域,使用Scoped服务 using (var scope = _serviceProvider.CreateScope()) { var myService = scope.ServiceProvider.GetRequiredService<IDocumentMoveData>(); var dbContext = scope.ServiceProvider.GetRequiredService<MyDBContext>(); List<DocumentInfo> files = myService.TemplateFiles(_instanceNumber); // 这里可以添加业务逻辑处理,比如处理files _logger.LogInformation("Instance {InstanceNumber} processed {Count} files", _instanceNumber, files?.Count ?? 0); } // 添加异步延迟,避免同步阻塞线程 await Task.Delay(TimeSpan.FromSeconds(5), stoppingToken); } _logger.LogInformation("Background service instance {InstanceNumber} stopped", _instanceNumber); } }
关键修改说明
- 闭包变量修复:通过
int currentInstanceNumber = i;在循环内部捕获当前循环的i值,确保每个服务实例拿到独立的参数。 - 作用域正确管理:将作用域创建移到
ExecuteAsync的循环内部,每次执行业务逻辑都使用新的Scoped服务实例,避免长期持有导致的资源问题。 - 异步逻辑完善:添加
await Task.Delay确保方法异步执行,不会阻塞托管服务的线程池。
内容的提问来源于stack exchange,提问作者Michael EstrinOne
相关产品推荐
相关产品推荐

