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

.NET Core 3.1 Web API控制器Action超时设置异常求助

.NET Core 3.1 Web API 控制器Action超时设置问题

我有一个基于.NET Core 3.1的Web API,希望为特定控制器Action设置不同的超时时间。我尝试创建了如下ActionFilter:

public class TimeOutAttribute : ActionFilterAttribute
{
    private readonly int _timeout;

    public TimeOutAttribute(int timeout)
    {
        _timeout = timeout;
    }

    public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
    {
        try
        {
            var cts = new CancellationTokenSource(TimeSpan.FromMilliseconds(_timeout));
            await Task.Run(async () => await next(), cts.Token);
        }
        catch (TaskCanceledException)
        {
            var request = context.HttpContext.Request;
            var message = $"Action exceeded the set timeout limit {_timeout} milisecond for {request.PathBase}{request.Path}";
            throw new ActionTimeOutException(message);
        }
    }
}

并在控制器方法上使用该特性:

[TimeOut(100)]
public async Task<IActionResult> Get()
{
}

但即使Get方法执行时间超过100ms,也未触发预期的异常。请问我的代码存在什么问题?或者有没有其他设置控制器Action超时的可行方案?


问题分析与解决方案

现有代码的问题

  • 错误使用Task.Run包装执行逻辑:next()本身代表Action的执行任务,用Task.Run将其包裹到新线程后,不仅会丢失原请求上下文,而且自定义的取消令牌并未真正传递到Action内部的异步操作中。即便超时触发TaskCanceledException,Action的逻辑可能仍在后台执行,无法被真正中断。
  • 取消令牌未关联请求上下文:.NET Core请求自带HttpContext.RequestAborted取消令牌,你的自定义令牌未与它关联,导致Action内部如果没有主动检查令牌,根本不会感知到超时取消信号。

修正后的ActionFilter实现

正确的做法是将自定义超时令牌与请求的取消令牌关联,并传递到Action执行流程中,确保Action能响应取消信号:

public class TimeOutAttribute : ActionFilterAttribute
{
    private readonly int _timeoutMilliseconds;

    public TimeOutAttribute(int timeoutMilliseconds)
    {
        _timeoutMilliseconds = timeoutMilliseconds;
    }

    public override async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
    {
        // 关联请求自带的取消令牌,保证请求中断时也能触发取消
        using var cts = CancellationTokenSource.CreateLinkedTokenSource(context.HttpContext.RequestAborted);
        cts.CancelAfter(TimeSpan.FromMilliseconds(_timeoutMilliseconds));

        try
        {
            // 将超时令牌注入到Action的参数中(如果Action接受CancellationToken参数)
            context.ActionArguments["cancellationToken"] = cts.Token;
            // 直接执行Action逻辑,确保令牌传递到位
            await next();
        }
        catch (OperationCanceledException) when (!context.HttpContext.RequestAborted.IsCancellationRequested)
        {
            var request = context.HttpContext.Request;
            var message = $"Action执行超时,超过设置的{_timeoutMilliseconds}毫秒限制,路径:{request.PathBase}{request.Path}";
            // 直接返回超时响应,无需抛出异常
            context.Result = new ObjectResult(message)
            {
                StatusCode = StatusCodes.Status408RequestTimeout
            };
            context.HttpContext.Response.Headers.Add("X-Timeout", "true");
        }
    }
}

使用注意事项

  1. 确保Action方法接受CancellationToken参数,让内部异步操作能响应超时取消:
[TimeOut(100)]
public async Task<IActionResult> Get(CancellationToken cancellationToken)
{
    // 所有异步操作都传入该令牌
    await SomeLongRunningAsyncOperation(cancellationToken);
    return Ok();
}
  1. 若无法修改Action参数,可在Action内部通过HttpContext.RequestAborted获取取消令牌,同样需要确保异步操作传入该令牌。

其他可选方案

  • 中间件实现路由级超时:如果需要为多个Action设置不同超时,可编写中间件,根据路由规则匹配对应超时时间,这种方式能更早介入请求流程,取消更彻底。
  • Polly超时策略:结合Polly库的超时Policy,在Action或服务层添加超时控制,还可搭配重试、熔断等策略使用:
[TimeOut(100)]
public async Task<IActionResult> Get(CancellationToken cancellationToken)
{
    var timeoutPolicy = Policy.TimeoutAsync(TimeSpan.FromMilliseconds(100));
    await timeoutPolicy.ExecuteAsync(async (ct) =>
    {
        await SomeLongRunningAsyncOperation(ct);
    }, cancellationToken);
    return Ok();
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.16 13:25:31