Blazor Server如何避免多作用域背景服务的代码重复?
解决方案:共享ScopedBackgroundService实现多定时任务服务
问题根源
当你将多个类注册为同一IScopedProcessingService接口的实现时,默认的.NET DI容器会覆盖之前的注册项,仅保留最后一个注册的服务实例,导致只有最后一个业务服务能被执行。
无代码重复的解决方法
下面提供三种可行方案,均遵循DRY原则:
方案一:泛型化基础服务
通过泛型接口和泛型BackgroundService,让每个业务服务对应独立的托管服务实例,同时共享核心逻辑。
- 定义泛型业务接口
public interface IScopedProcessingService<T> where T : class, IScopedProcessingService<T> { Task DoWorkAsync(CancellationToken stoppingToken); }
- 业务服务实现泛型接口
public class PhaseChangeReportService : IScopedProcessingService<PhaseChangeReportService> { public async Task DoWorkAsync(CancellationToken stoppingToken) { // 午夜发送活动邮件的业务逻辑 } } public class PlannerOrderStatusUpdateService : IScopedProcessingService<PlannerOrderStatusUpdateService> { public async Task DoWorkAsync(CancellationToken stoppingToken) { // 每日解析Excel更新数据库的业务逻辑 } }
- 泛型ScopedBackgroundService基类
public class ScopedBackgroundService<T> : BackgroundService where T : class, IScopedProcessingService<T> { private readonly IServiceScopeFactory _serviceScopeFactory; private readonly ILogger<ScopedBackgroundService<T>> _logger; public ScopedBackgroundService(IServiceScopeFactory serviceScopeFactory, ILogger<ScopedBackgroundService<T>> logger) { _serviceScopeFactory = serviceScopeFactory; _logger = logger; } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { while (!stoppingToken.IsCancellationRequested) { using var scope = _serviceScopeFactory.CreateScope(); var processingService = scope.ServiceProvider.GetRequiredService<T>(); await processingService.DoWorkAsync(stoppingToken); // 可根据业务调整间隔,或移至业务服务内部处理 await Task.Delay(TimeSpan.FromHours(24), stoppingToken); } } }
- Program.cs注册服务
builder.Services.AddScoped<PhaseChangeReportService>(); builder.Services.AddHostedService<ScopedBackgroundService<PhaseChangeReportService>>(); builder.Services.AddScoped<PlannerOrderStatusUpdateService>(); builder.Services.AddHostedService<ScopedBackgroundService<PlannerOrderStatusUpdateService>>();
方案二:集合注入批量处理
将所有业务服务注册为IScopedProcessingService集合,让单个BackgroundService遍历执行所有服务(适合执行时间一致的场景)。
- 修改ScopedBackgroundService
public class ScopedBackgroundService : BackgroundService { private readonly IServiceScopeFactory _serviceScopeFactory; private readonly ILogger<ScopedBackgroundService> _logger; public ScopedBackgroundService(IServiceScopeFactory serviceScopeFactory, ILogger<ScopedBackgroundService> logger) { _serviceScopeFactory = serviceScopeFactory; _logger = logger; } protected override async Task ExecuteAsync(CancellationToken stoppingToken) { while (!stoppingToken.IsCancellationRequested) { using var scope = _serviceScopeFactory.CreateScope(); var allServices = scope.ServiceProvider.GetServices<IScopedProcessingService>(); foreach (var service in allServices) { await service.DoWorkAsync(stoppingToken); } await Task.Delay(TimeSpan.FromHours(24), stoppingToken); } } }
- Program.cs注册服务
builder.Services.AddScoped<IScopedProcessingService, PhaseChangeReportService>(); builder.Services.AddScoped<IScopedProcessingService, PlannerOrderStatusUpdateService>(); builder.Services.AddHostedService<ScopedBackgroundService>();
方案三:抽象基类封装通用逻辑
将BackgroundService的通用逻辑抽成抽象基类,每个业务服务对应一个轻量的托管服务子类,独立控制执行间隔。
- 抽象基类
public abstract class BaseScopedBackgroundService : BackgroundService { private readonly IServiceScopeFactory _serviceScopeFactory; private readonly ILogger<BaseScopedBackgroundService> _logger; protected BaseScopedBackgroundService(IServiceScopeFactory serviceScopeFactory, ILogger<BaseScopedBackgroundService> logger) { _serviceScopeFactory = serviceScopeFactory; _logger = logger; } protected abstract Task DoScopedWorkAsync(IServiceScope scope, CancellationToken stoppingToken); protected abstract TimeSpan GetDelayInterval(); protected override async Task ExecuteAsync(CancellationToken stoppingToken) { while (!stoppingToken.IsCancellationRequested) { using var scope = _serviceScopeFactory.CreateScope(); await DoScopedWorkAsync(scope, stoppingToken); await Task.Delay(GetDelayInterval(), stoppingToken); } } }
- 业务托管服务子类
public class PhaseChangeReportBackgroundService : BaseScopedBackgroundService { public PhaseChangeReportBackgroundService(IServiceScopeFactory serviceScopeFactory, ILogger<PhaseChangeReportBackgroundService> logger) : base(serviceScopeFactory, logger) { } protected override async Task DoScopedWorkAsync(IServiceScope scope, CancellationToken stoppingToken) { var service = scope.ServiceProvider.GetRequiredService<PhaseChangeReportService>(); await service.DoWorkAsync(stoppingToken); } protected override TimeSpan GetDelayInterval() { // 计算到下一个午夜的间隔 var now = DateTime.Now; var nextMidnight = now.Date.AddDays(1); return nextMidnight - now; } } public class PlannerOrderStatusUpdateBackgroundService : BaseScopedBackgroundService { public PlannerOrderStatusUpdateBackgroundService(IServiceScopeFactory serviceScopeFactory, ILogger<PlannerOrderStatusUpdateBackgroundService> logger) : base(serviceScopeFactory, logger) { } protected override async Task DoScopedWorkAsync(IServiceScope scope, CancellationToken stoppingToken) { var service = scope.ServiceProvider.GetRequiredService<PlannerOrderStatusUpdateService>(); await service.DoWorkAsync(stoppingToken); } protected override TimeSpan GetDelayInterval() { // 计算到下一个凌晨2点的间隔 var now = DateTime.Now; var nextRun = now.Date.AddHours(2); if (nextRun <= now) nextRun = nextRun.AddDays(1); return nextRun - now; } }
- Program.cs注册服务
builder.Services.AddScoped<PhaseChangeReportService>(); builder.Services.AddHostedService<PhaseChangeReportBackgroundService>(); builder.Services.AddScoped<PlannerOrderStatusUpdateService>(); builder.Services.AddHostedService<PlannerOrderStatusUpdateBackgroundService>();
内容的提问来源于stack exchange,提问作者Neil Wehneman
相关产品推荐
相关产品推荐

