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

使用Semaphore在Middleware中处理单请求时请求丢失的问题排查

问题分析与修复方案

你的代码存在几个关键问题,导致后续请求被丢弃:

  1. 直接存储HttpContext是致命错误
    ASP.NET Core的HttpContext是和当前请求绑定的临时对象,请求结束后框架会回收复用它。你把它入队后,等后续处理时,这个上下文已经失效,里面的请求信息、响应对象都不能正常工作了。

  2. 队列处理逻辑混乱
    你在finally块里递归调用InvokeAsync,这会导致上下文处理流程被打乱,而且后续入队的请求没有可靠的触发机制——如果没有新的请求进来触发InvokeAsync,队列里的请求就永远不会被处理。

  3. WaitAsync(TimeSpan.Zero)的使用方式不对
    这个调用会立即返回,后续请求直接入队就返回,但此时没有主动去唤醒队列处理,只有当锁释放后的递归才会尝试,但这个逻辑不可靠。


修正后的代码

我们改用存储请求处理委托,而不是HttpContext,同时用后台循环来确保队列里的请求被依次处理:

using Microsoft.AspNetCore.Http;
using System.Collections.Concurrent;

public class OneRequestAtATimeMiddleware
{
    private readonly RequestDelegate _next;
    private readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
    private readonly ConcurrentQueue<Func<Task>> _requestQueue = new ConcurrentQueue<Func<Task>>();
    private bool _isProcessingQueue;

    public OneRequestAtATimeMiddleware(RequestDelegate next)
    {
        _next = next;
    }

    public async Task InvokeAsync(HttpContext context)
    {
        var endpoint = context.GetEndpoint();
        if (endpoint?.Metadata?.GetMetadata<OneRequestAtATime>() != null)
        {
            // 将当前请求的处理逻辑包装成委托入队
            var requestTask = new Func<Task>(async () => await _next(context));
            _requestQueue.Enqueue(requestTask);

            // 尝试启动队列处理,避免重复启动
            await TryProcessQueueAsync();
        }
        else
        {
            await _next(context);
        }
    }

    private async Task TryProcessQueueAsync()
    {
        // 用双重检查锁定,确保只有一个线程在处理队列
        if (_isProcessingQueue)
            return;

        try
        {
            await _semaphore.WaitAsync();
            if (_isProcessingQueue)
                return;

            _isProcessingQueue = true;
        }
        finally
        {
            _semaphore.Release();
        }

        try
        {
            // 循环处理队列里的所有请求
            while (_requestQueue.TryDequeue(out var requestTask))
            {
                await requestTask();
            }
        }
        finally
        {
            _isProcessingQueue = false;
        }
    }

    [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
    public class OneRequestAtATime : Attribute { }
}

关键改动说明

  • 存储请求处理委托:不再直接保存HttpContext,而是把_next(context)包装成Func<Task>入队,这样每个请求的处理逻辑都是独立且有效的。
  • 独立的队列处理方法:TryProcessQueueAsync负责启动队列处理循环,用双重检查锁定确保同一时间只有一个线程在处理队列,避免重复执行。
  • 可靠的触发机制:每个请求入队后都会尝试启动队列处理,不管当前锁是否可用,确保队列里的请求不会被遗漏。

这样就能保证标注了[OneRequestAtATime]的Action方法,同一时间只有一个请求在处理,其余请求会排队依次执行。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 12:27:24