.NET 6 API开发调试模式下CancellationToken取消异常配置方案
问题场景
你有一个继承自Microsoft.AspNetCore.Mvc.ControllerBase的Webhook接口,代码如下:
[HttpPost] public async Task<IActionResult> ReceiveMessage(CancellationToken cancellationToken) { // 异步业务逻辑,会将cancellationToken传入下游异步方法 var players = _dbContext.Players; players.ToListAsync(cancellationToken); // 本地调试时此处会抛出"A task was cancelled"错误 return Ok(); }
该接口生产环境运行正常,无超时问题,业务逻辑可完整执行。但本地调试时,如果断点停留时长超过请求超时阈值,请求会被ASP.NET Core自动标记为取消,传入Action的CancellationToken会触发取消状态,进而抛出任务取消异常。需求是在开发/调试环境下替换自动注入的CancellationToken为CancellationToken.None,禁用开发环境下的令牌取消逻辑。
注:你提到OnActionExecuting不属于ControllerBase为认知偏差,ControllerBase本身提供了OnActionExecuting虚方法可重写,不过以下方案不需要重写控制器方法即可实现需求。
可行实现方案
方案1:全局Action过滤器(推荐,无业务代码侵入)
自定义仅在开发环境生效的Action过滤器,自动替换所有Action参数中的CancellationToken为CancellationToken.None,不需要修改现有业务代码:
- 实现过滤器:
public class DevNoCancellationFilter : IActionFilter { private readonly IWebHostEnvironment _hostEnv; public DevNoCancellationFilter(IWebHostEnvironment hostEnv) { _hostEnv = hostEnv; } public void OnActionExecuting(ActionExecutingContext context) { // 非开发环境直接跳过,不影响生产逻辑 if (!_hostEnv.IsDevelopment()) return; // 查找所有CancellationToken类型的Action参数,替换为无取消逻辑的令牌 var tokenParams = context.ActionArguments .Where(p => p.Value is CancellationToken) .Select(p => p.Key) .ToList(); foreach (var paramKey in tokenParams) { context.ActionArguments[paramKey] = CancellationToken.None; } } public void OnActionExecuted(ActionExecutedContext context) { // 无需额外处理 } }
- 在Program.cs中注册过滤器:
builder.Services.AddControllers(opts => { opts.Filters.Add<DevNoCancellationFilter>(); });
注册后生产环境逻辑完全不受影响,开发环境下所有Action注入的CancellationToken都不会触发取消。
方案2:Action内局部处理
如果不想全局生效,可以在单个接口内判断当前环境,选择使用的令牌:
// 先在控制器构造函数注入IWebHostEnvironment private readonly IWebHostEnvironment _env; public WebhookController(IWebHostEnvironment env) { _env = env; } [HttpPost] public async Task<IActionResult> ReceiveMessage(CancellationToken cancellationToken) { // 开发环境使用无取消逻辑的令牌,其他环境使用默认请求令牌 var workToken = _env.IsDevelopment() ? CancellationToken.None : cancellationToken; var players = _dbContext.Players; // 注意原示例代码漏了await,实际使用必须加,否则会引发未观测任务异常 await players.ToListAsync(workToken); return Ok(); }
方案3:调大开发环境请求超时时间
如果不想替换令牌,也可以直接把开发环境的请求超时阈值调到足够覆盖调试时长,从根源避免调试时触发请求取消:
// Program.cs中仅开发环境配置Kestrel超时 if (builder.Environment.IsDevelopment()) { builder.WebHost.ConfigureKestrel(opts => { opts.Limits.RequestHeadersTimeout = TimeSpan.FromMinutes(30); opts.Limits.KeepAliveTimeout = TimeSpan.FromMinutes(30); }); }
如果本地使用IIS Express调试,可直接修改IIS Express配置文件中的连接超时参数,效果一致。
注意:生产环境绝对不要全局替换
CancellationToken为CancellationToken.None。默认注入的请求关联令牌会在客户端断开连接、请求超时的时候及时终止异步任务,避免无效计算和数据库连接占用,是ASP.NET Core的默认资源保护机制。
内容的提问来源于stack exchange,提问作者ElConrado

