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

Blazor Server项目如何添加触发后持续运行至结束的后台服务?

在Blazor Server中实现持久运行的后台任务

完全可以实现,你之前的问题出在服务生命周期绑定上——默认的Scoped服务会和页面的Circuit/组件生命周期绑定,页面销毁时服务也会被回收,导致任务中断。下面提供两种实用的解决方案:

方案一:Singleton任务管理器(适合简单场景)

创建一个Singleton生命周期的服务,专门管理后台任务,确保页面跳转后任务仍能持续运行。

1. 实现任务管理器服务

public class BackgroundTaskManager
{
    private readonly IServiceScopeFactory _scopeFactory;
    private readonly List<Task> _runningTasks = new();

    public BackgroundTaskManager(IServiceScopeFactory scopeFactory)
    {
        _scopeFactory = scopeFactory;
    }

    public async Task StartDatabaseMigrationTaskAsync()
    {
        // 创建独立服务范围,避免依赖Scoped服务的生命周期问题
        using var scope = _scopeFactory.CreateScope();
        var sourceDb = scope.ServiceProvider.GetRequiredService<YourSourceDbContext>();
        var targetDb = scope.ServiceProvider.GetRequiredService<YourTargetDbContext>();

        var task = Task.Run(async () =>
        {
            try
            {
                // 遍历源数据库(使用AsNoTracking减少内存占用)
                var items = await sourceDb.LargeTable.AsNoTracking().ToListAsync();
                foreach (var item in items)
                {
                    if (item.MeetsFilterCondition())
                    {
                        targetDb.TargetTable.Add(item.MapToTargetEntity());
                        // 每1000条数据提交一次,避免事务过大
                        if (targetDb.TargetTable.Local.Count % 1000 == 0)
                        {
                            await targetDb.SaveChangesAsync();
                            targetDb.ChangeTracker.Clear();
                        }
                    }
                }
                // 提交剩余数据
                await targetDb.SaveChangesAsync();
            }
            finally
            {
                lock (_runningTasks)
                {
                    _runningTasks.Remove(Task.CurrentId);
                }
            }
        });

        lock (_runningTasks)
        {
            _runningTasks.Add(task);
        }

        await task;
    }
}

2. 注册Singleton服务

在Program.cs(或.NET 5及更早版本的Startup.cs)中注册:

builder.Services.AddSingleton<BackgroundTaskManager>();

3. 在Razor页面触发任务

@inject BackgroundTaskManager TaskManager
@inject NavigationManager NavManager

<button @onclick="TriggerMigration">启动数据迁移任务</button>

@code {
    private async Task TriggerMigration()
    {
        // 用异步非阻塞方式启动任务,避免卡住UI
        _ = TaskManager.StartDatabaseMigrationTaskAsync();
        // 立即跳转到其他页面,任务不受影响
        NavManager.NavigateTo("/dashboard");
    }
}

方案二:托管服务+任务队列(适合多任务场景)

使用.NET内置的BackgroundService实现更规范的任务队列,支持批量提交、任务调度和取消控制。

1. 实现任务队列接口与类

public interface IBackgroundTaskQueue
{
    void QueueBackgroundWorkItem(Func<CancellationToken, Task> workItem);
}

public class BackgroundTaskQueue : IBackgroundTaskQueue
{
    private readonly ConcurrentQueue<Func<CancellationToken, Task>> _workItems = new();
    private readonly SemaphoreSlim _signal = new(0);

    public void QueueBackgroundWorkItem(Func<CancellationToken, Task> workItem)
    {
        if (workItem == null) throw new ArgumentNullException(nameof(workItem));
        
        _workItems.Enqueue(workItem);
        _signal.Release();
    }

    public async Task<Func<CancellationToken, Task>> DequeueAsync(CancellationToken cancellationToken)
    {
        await _signal.WaitAsync(cancellationToken);
        _workItems.TryDequeue(out var workItem);
        return workItem;
    }
}

2. 实现托管服务

public class QueuedHostedService : BackgroundService
{
    private readonly ILogger<QueuedHostedService> _logger;
    private readonly IBackgroundTaskQueue _taskQueue;
    private readonly IServiceScopeFactory _scopeFactory;

    public QueuedHostedService(ILogger<QueuedHostedService> logger, IBackgroundTaskQueue taskQueue, IServiceScopeFactory scopeFactory)
    {
        _logger = logger;
        _taskQueue = taskQueue;
        _scopeFactory = scopeFactory;
    }

    protected override async Task ExecuteAsync(CancellationToken stoppingToken)
    {
        _logger.LogInformation("后台任务队列服务已启动");

        while (!stoppingToken.IsCancellationRequested)
        {
            var workItem = await _taskQueue.DequeueAsync(stoppingToken);

            try
            {
                using var scope = _scopeFactory.CreateScope();
                await workItem(stoppingToken);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "执行后台任务时出错");
            }
        }

        _logger.LogInformation("后台任务队列服务已停止");
    }
}

3. 注册服务

builder.Services.AddSingleton<IBackgroundTaskQueue, BackgroundTaskQueue>();
builder.Services.AddHostedService<QueuedHostedService>();

4. 在Razor页面提交任务

@inject IBackgroundTaskQueue TaskQueue
@inject NavigationManager NavManager

<button @onclick="AddMigrationTask">启动数据迁移任务</button>

@code {
    private void AddMigrationTask()
    {
        TaskQueue.QueueBackgroundWorkItem(async token =>
        {
            using var scope = _scopeFactory.CreateScope();
            var sourceDb = scope.ServiceProvider.GetRequiredService<YourSourceDbContext>();
            var targetDb = scope.ServiceProvider.GetRequiredService<YourTargetDbContext>();

            // 数据库操作逻辑(同方案一)
            var items = await sourceDb.LargeTable.AsNoTracking().ToListAsync(token);
            foreach (var item in items)
            {
                if (token.IsCancellationRequested) break;
                
                if (item.MeetsFilterCondition())
                {
                    targetDb.TargetTable.Add(item.MapToTargetEntity());
                    if (targetDb.TargetTable.Local.Count % 1000 == 0)
                    {
                        await targetDb.SaveChangesAsync(token);
                        targetDb.ChangeTracker.Clear();
                    }
                }
            }
            await targetDb.SaveChangesAsync(token);
        });

        NavManager.NavigateTo("/other-page");
    }
}

关键注意事项

  • 生命周期选择:必须使用Singleton或HostedService,避免Scoped服务与页面生命周期绑定导致的任务中断。
  • 数据库优化:使用AsNoTracking()减少内存开销,分批提交数据避免单次事务过大。
  • 取消控制:任务中监听取消令牌,防止应用关闭时出现数据不一致。
  • 进度跟踪(可选):若需展示任务进度,可在服务中添加进度字段,通过SignalR实时推送给前端。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 13:50:29