.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"); } } }
使用注意事项
- 确保Action方法接受
CancellationToken参数,让内部异步操作能响应超时取消:
[TimeOut(100)] public async Task<IActionResult> Get(CancellationToken cancellationToken) { // 所有异步操作都传入该令牌 await SomeLongRunningAsyncOperation(cancellationToken); return Ok(); }
- 若无法修改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
相关产品推荐
相关产品推荐

