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

Blazor Server如何避免多作用域背景服务的代码重复?

解决方案:共享ScopedBackgroundService实现多定时任务服务

问题根源

当你将多个类注册为同一IScopedProcessingService接口的实现时,默认的.NET DI容器会覆盖之前的注册项,仅保留最后一个注册的服务实例,导致只有最后一个业务服务能被执行。

无代码重复的解决方法

下面提供三种可行方案,均遵循DRY原则:

方案一:泛型化基础服务

通过泛型接口和泛型BackgroundService,让每个业务服务对应独立的托管服务实例,同时共享核心逻辑。

  1. 定义泛型业务接口
public interface IScopedProcessingService<T> where T : class, IScopedProcessingService<T>
{
    Task DoWorkAsync(CancellationToken stoppingToken);
}
  1. 业务服务实现泛型接口
public class PhaseChangeReportService : IScopedProcessingService<PhaseChangeReportService>
{
    public async Task DoWorkAsync(CancellationToken stoppingToken)
    {
        // 午夜发送活动邮件的业务逻辑
    }
}

public class PlannerOrderStatusUpdateService : IScopedProcessingService<PlannerOrderStatusUpdateService>
{
    public async Task DoWorkAsync(CancellationToken stoppingToken)
    {
        // 每日解析Excel更新数据库的业务逻辑
    }
}
  1. 泛型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);
        }
    }
}
  1. Program.cs注册服务
builder.Services.AddScoped<PhaseChangeReportService>();
builder.Services.AddHostedService<ScopedBackgroundService<PhaseChangeReportService>>();

builder.Services.AddScoped<PlannerOrderStatusUpdateService>();
builder.Services.AddHostedService<ScopedBackgroundService<PlannerOrderStatusUpdateService>>();

方案二:集合注入批量处理

将所有业务服务注册为IScopedProcessingService集合,让单个BackgroundService遍历执行所有服务(适合执行时间一致的场景)。

  1. 修改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);
        }
    }
}
  1. Program.cs注册服务
builder.Services.AddScoped<IScopedProcessingService, PhaseChangeReportService>();
builder.Services.AddScoped<IScopedProcessingService, PlannerOrderStatusUpdateService>();
builder.Services.AddHostedService<ScopedBackgroundService>();

方案三:抽象基类封装通用逻辑

将BackgroundService的通用逻辑抽成抽象基类,每个业务服务对应一个轻量的托管服务子类,独立控制执行间隔。

  1. 抽象基类
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);
        }
    }
}
  1. 业务托管服务子类
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;
    }
}
  1. Program.cs注册服务
builder.Services.AddScoped<PhaseChangeReportService>();
builder.Services.AddHostedService<PhaseChangeReportBackgroundService>();

builder.Services.AddScoped<PlannerOrderStatusUpdateService>();
builder.Services.AddHostedService<PlannerOrderStatusUpdateBackgroundService>();

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.19 23:35:38