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

如何在调用GetAllUsers控制器时并行运行后台服务

实现控制器与后台服务并行运行的方案

先指出你原代码里的两个问题:

  • StopAsync方法自己调用自己,属于无限递归,直接会造成栈溢出,得先删掉这个逻辑。
  • 当前代码里的await Task.Delay(10秒)是同步等待的,控制器会卡10秒才返回结果,完全没实现并行的需求。

下面给你两种可行的实现方案:


方案一:轻量快速实现(适合简单场景)

把后台要执行的逻辑抽成独立方法,在控制器里直接启动任务但不等待它完成,控制器立马返回结果。注意一定要给后台任务加异常捕获,避免没处理的异常搞崩整个应用。

修改后的控制器代码:

private readonly ILogger<UsersController> _logger;

public UsersController(ILogger<UsersController> logger)
{
    _logger = logger;
}

[HttpGet]
public async Task<IActionResult> GetAllUsers(CancellationToken stoppingToken)
{
    // 启动后台任务,不等待完成,用下划线忽略返回的Task
    _ = RunBackgroundTask(stoppingToken);

    // 直接准备并返回用户数据,不用等后台任务
    _logger.LogInformation("开始返回用户数据");
    List<UserDto> users = listUsers;
    return new OkObjectResult(users);
}

// 单独封装后台任务的逻辑
private async Task RunBackgroundTask(CancellationToken cancellationToken)
{
    try
    {
        _logger.LogInformation("后台服务启动于: {time}", DateTimeOffset.Now);
        await Task.Delay(TimeSpan.FromSeconds(10), cancellationToken);
        _logger.LogInformation("执行用户数据处理逻辑");
        
        // 这里替换成你实际要执行的后台业务代码,比如数据同步、日志分析等
        
        _logger.LogInformation("后台服务结束于: {time}", DateTimeOffset.Now);
    }
    catch (OperationCanceledException)
    {
        _logger.LogInformation("后台服务被取消");
    }
    catch (Exception ex)
    {
        _logger.LogError(ex, "后台服务执行出错");
    }
}

方案二:队列式后台服务(适合生产环境)

如果你的后台任务比较重要,怕因为应用池回收导致任务丢失,推荐用这种基于队列的后台服务方案,它通过ASP.NET Core的IHostedService来管理任务,可靠性更高。

步骤1:创建任务队列

// 定义队列接口
public interface IBackgroundTaskQueue
{
    void QueueBackgroundWorkItem(Func<CancellationToken, Task> workItem);
    Task<Func<CancellationToken, Task>> DequeueAsync(CancellationToken cancellationToken);
}

// 队列实现
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;

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

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

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

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

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

步骤3:注册服务

在Program.cs里添加以下代码,把队列和后台服务注入到DI容器:

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

步骤4:修改控制器代码

private readonly ILogger<UsersController> _logger;
private readonly IBackgroundTaskQueue _taskQueue;

// 构造函数注入任务队列
public UsersController(ILogger<UsersController> logger, IBackgroundTaskQueue taskQueue)
{
    _logger = logger;
    _taskQueue = taskQueue;
}

[HttpGet]
public async Task<IActionResult> GetAllUsers(CancellationToken stoppingToken)
{
    // 把后台任务加入队列,由后台服务自动处理
    _taskQueue.QueueBackgroundWorkItem(async token =>
    {
        _logger.LogInformation("后台服务启动于: {time}", DateTimeOffset.Now);
        await Task.Delay(TimeSpan.FromSeconds(10), token);
        _logger.LogInformation("执行用户数据处理逻辑");
        
        // 这里替换成你的实际后台业务代码
        
        _logger.LogInformation("后台服务结束于: {time}", DateTimeOffset.Now);
    });

    // 立即返回用户数据
    _logger.LogInformation("开始返回用户数据");
    List<UserDto> users = listUsers;
    return new OkObjectResult(users);
}

这种方案的好处是,后台任务会被队列管理,就算控制器已经返回结果,后台服务也会持续处理队列里的任务,还能避免因为应用池回收导致任务丢失(如果需要更持久化,还可以结合数据库做任务持久化)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.09 13:52:50