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

.NET 6 API如何避免HTTP请求被取消后终止执行?

.NET 6 API:请求取消时后台任务不中断的解决方案

一、为什么Postman和React前端取消请求的后端表现不同?

  • React前端取消请求:使用AbortController取消请求时,浏览器会主动关闭TCP连接(发送RST包),Kestrel服务器检测到连接中断后,会立即触发请求关联的CancellationToken,ASP.NET Core的默认请求管道会终止当前处理流程,因此后续数据库操作代码会被中断,无法执行完成。
  • Postman取消请求:Postman的取消逻辑通常是发送TCP FIN包(优雅关闭),Kestrel会等待当前请求处理完成后再关闭连接;另外在调试模式下,当OperationCanceledException抛出时,调试器会暂停程序,你手动继续执行后,代码才会完成剩余逻辑。部分场景下Postman只是停止接收响应,并未真正触发服务器端的取消令牌,所以服务器端代码会正常执行完毕。

二、如何改为无需等待响应的执行模式?

要实现请求取消后仍能完成数据库操作,核心是把业务逻辑从请求管道中剥离,放到独立的后台执行流程中,不受请求生命周期影响。推荐使用后台任务队列的方式,这是ASP.NET Core官方推荐的可靠方案。

1. 实现后台任务队列

首先定义队列接口和实现类,用于存放待执行的后台任务:

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

public class BackgroundTaskQueue : IBackgroundTaskQueue
{
    private readonly Channel<Func<CancellationToken, Task>> _queue;

    public BackgroundTaskQueue(int capacity)
    {
        var options = new BoundedChannelOptions(capacity)
        {
            FullMode = BoundedChannelFullMode.Wait
        };
        _queue = Channel.CreateBounded<Func<CancellationToken, Task>>(options);
    }

    public async void QueueBackgroundWorkItem(Func<CancellationToken, Task> workItem)
    {
        if (workItem == null) throw new ArgumentNullException(nameof(workItem));
        await _queue.Writer.WriteAsync(workItem);
    }

    public async Task<Func<CancellationToken, Task>> DequeueAsync(CancellationToken cancellationToken)
    {
        return await _queue.Reader.ReadAsync(cancellationToken);
    }
}

2. 实现后台任务处理服务

创建一个HostedService来持续处理队列中的任务:

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("Queued hosted service started.");

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

            try
            {
                await workItem(stoppingToken);
            }
            catch (Exception ex)
            {
                _logger.LogError(ex, "Failed to execute background task");
            }
        }

        _logger.LogInformation("Queued hosted service stopping.");
    }
}

3. 注册服务到依赖注入容器

在Program.cs中注册队列和后台服务:

builder.Services.AddSingleton<IBackgroundTaskQueue>(_ => new BackgroundTaskQueue(100));
builder.Services.AddHostedService<QueuedHostedService>();

4. 在控制器中使用后台队列

修改API控制器,将数据库操作放入后台队列,立即返回响应:

[ApiController]
[Route("api/[controller]")]
public class DataController : ControllerBase
{
    private readonly IBackgroundTaskQueue _taskQueue;
    private readonly AppDbContext _dbContext;

    public DataController(IBackgroundTaskQueue taskQueue, AppDbContext dbContext)
    {
        _taskQueue = taskQueue;
        _dbContext = dbContext;
    }

    [HttpPost]
    public IActionResult CreateData([FromBody] DataModel model)
    {
        // 将数据库操作加入后台队列
        _taskQueue.QueueBackgroundWorkItem(async token =>
        {
            var newData = new DataModel
            {
                Name = model.Name,
                Value = model.Value
                // 其他字段赋值
            };

            _dbContext.Data.Add(newData);
            await _dbContext.SaveChangesAsync(token);
        });

        // 立即返回接受状态,无需等待操作完成
        return Accepted();
    }
}

备选方案(不推荐)

如果只是快速验证,也可以使用Task.Run创建独立任务,但这种方式缺乏错误处理、任务监控和重试机制,且应用重启时未完成的任务会丢失:

[HttpPost]
public IActionResult CreateData([FromBody] DataModel model)
{
    // 使用独立的取消令牌,不受请求取消影响
    using var cts = new CancellationTokenSource();
    _ = Task.Run(async () =>
    {
        var newData = new DataModel
        {
            Name = model.Name,
            Value = model.Value
        };

        using var scope = HttpContext.RequestServices.CreateScope();
        var dbContext = scope.ServiceProvider.GetRequiredService<AppDbContext>();
        dbContext.Data.Add(newData);
        await dbContext.SaveChangesAsync(cts.Token);
    }, cts.Token);

    return Accepted();
}

注意:这里需要创建新的依赖注入 scope,因为原请求的scope会在请求结束后被释放。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.03 17:51:30