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

如何在ASP.NET Core 6 Web API中实现多请求排队?

ASP.NET Core 6 Web API 打印请求串行排队实现方案

一、核心思路

用单线程任务队列串行处理所有打印请求,同时通过创建独立服务作用域解决DBContext丢失问题——之前的方案之所以丢DBContext,是因为直接把请求线程里的Scoped对象传到后台线程,请求结束后DBContext会被框架回收;而在队列任务里单独创建作用域,就能保证DBContext和任务生命周期绑定,不会提前释放。

二、具体实现步骤

1. 注册单例任务队列服务

先写一个线程安全的任务队列,确保同一时间只有一个任务在执行:

public class PrintTaskQueue
{
    private readonly ConcurrentQueue<Func<Task>> _tasks = new();
    private readonly SemaphoreSlim _semaphore = new(0);
    private readonly IServiceProvider _serviceProvider;
    private bool _isProcessing;

    public PrintTaskQueue(IServiceProvider serviceProvider)
    {
        _serviceProvider = serviceProvider;
    }

    // 入队打印任务,参数是需要作用域的委托
    public void EnqueuePrintTask(Func<IServiceScope, Task> printTask)
    {
        _tasks.Enqueue(async () =>
        {
            // 每个任务单独创建服务作用域
            using var scope = _serviceProvider.CreateScope();
            await printTask(scope);
        });
        _semaphore.Release();
        StartProcessingIfNeeded();
    }

    // 启动任务处理循环,确保只开一个线程处理
    private void StartProcessingIfNeeded()
    {
        if (_isProcessing) return;

        _isProcessing = true;
        Task.Run(async () =>
        {
            while (true)
            {
                await _semaphore.WaitAsync();
                if (_tasks.TryDequeue(out var task))
                {
                    try
                    {
                        await task();
                    }
                    catch (Exception ex)
                    {
                        // 这里加日志记录,比如打印失败的原因
                    }
                }
                else
                {
                    _isProcessing = false;
                    break;
                }
            }
        });
    }
}

然后在Program.cs里把这个队列注册成单例:

builder.Services.AddSingleton<PrintTaskQueue>();

2. 实现Scoped打印服务

把打印逻辑和DBContext操作封装到Scoped服务里,这样每次任务都会拿到新鲜的DBContext:

public class PrintService
{
    private readonly AppDbContext _dbContext;

    public PrintService(AppDbContext dbContext)
    {
        _dbContext = dbContext;
    }

    public async Task ProcessPrintJob(PrintObject printObj)
    {
        // 这里放心用DBContext,因为在当前任务的作用域内
        // 比如先把打印记录存到数据库
        var printLog = new PrintLog
        {
            Content = printObj.Content,
            CreateTime = DateTime.Now
        };
        _dbContext.PrintLogs.Add(printLog);
        await _dbContext.SaveChangesAsync();

        // 调用打印机SDK执行实际打印
        await SendToPrinter(printObj);
    }

    private Task SendToPrinter(PrintObject printObj)
    {
        // 模拟打印耗时操作,替换成实际的打印机调用逻辑
        return Task.Delay(2000);
    }
}

同样在Program.cs注册成Scoped:

builder.Services.AddScoped<PrintService>();

3. API控制器处理请求

控制器里只负责把请求丢进队列,不用等打印完成,直接返回响应:

[ApiController]
[Route("api/print")]
public class PrintController : ControllerBase
{
    private readonly PrintTaskQueue _printTaskQueue;

    public PrintController(PrintTaskQueue printTaskQueue)
    {
        _printTaskQueue = printTaskQueue;
    }

    [HttpPost]
    public IActionResult TriggerPrint([FromBody] PrintObject printObj)
    {
        _printTaskQueue.EnqueuePrintTask(async scope =>
        {
            // 从当前作用域获取打印服务
            var printService = scope.ServiceProvider.GetRequiredService<PrintService>();
            await printService.ProcessPrintJob(printObj);
        });

        return Ok("打印请求已排队,稍后处理");
    }
}

三、要注意的几个点

  • DBContext不丢的关键:必须在任务内部创建IServiceScope,通过Scope获取的服务都是当前任务专属的,不会被其他请求回收。
  • 串行执行保证:队列的处理循环是单线程的,加上SemaphoreSlim控制任务触发,确保同一时间只有一个打印任务在跑。
  • 异常不崩队列:任务执行时加try-catch,单个任务失败不会导致整个队列停摆,同时可以记录异常日志排查问题。
  • 接口响应快:客户端调用API后立即收到返回,不用等打印完成,体验更好。

四、可选替代方案:Hangfire

如果需要更高级的功能(比如任务重试、后台监控),可以用Hangfire:

  • 安装Hangfire相关包,配置存储(比如SQL Server)
  • 发布打印任务时指定同一个队列名,然后配置Hangfire只启动一个Worker处理该队列,就能实现串行执行。不过这个方案比自定义队列重一些,简单场景用自定义队列足够。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.31 11:45:24