如何为Controller添加CancellationToken以取消重复请求?
解决「新请求发起时取消旧请求」的CancellationToken实现方案
先明确两个核心问题:
控制器中CancellationToken的来源:
在ASP.NET Core里,只要你在控制器Action方法中声明CancellationToken参数,框架会自动注入这个token——它默认绑定的是客户端请求断开的信号(比如用户关闭页面、主动中断请求),但这没法直接实现「新请求取消旧请求」的需求,得额外做自定义逻辑。实现「新请求取消旧请求」的具体逻辑:
需要自己维护一个「请求标识-取消源」的映射,每次新请求进来时,先取消同标识的旧请求,再创建新的取消源绑定到当前请求。
完整代码示例
1. 控制器层实现
用线程安全的ConcurrentDictionary存储每个查询对应的取消源,避免多请求并发冲突:
using System.Collections.Concurrent; using System.Threading; [ApiController] [Route("[controller]")] public class UserController : ControllerBase { private readonly IUserService _userService; // 以查询字符串为key,存储对应请求的取消源 private static readonly ConcurrentDictionary<string, CancellationTokenSource> _queryTokenSources = new(); public UserController(IUserService userService) { _userService = userService; } [HttpGet("getUsers")] public async Task<IEnumerable<string>> GetUsersAsync(string query) { // 1. 取消当前查询的旧请求 if (_queryTokenSources.TryRemove(query, out var oldTokenSource)) { oldTokenSource.Cancel(); oldTokenSource.Dispose(); } // 2. 创建新的取消源,同时绑定框架自带的客户端断开token using var newTokenSource = CancellationTokenSource.CreateLinkedTokenSource(HttpContext.RequestAborted); _queryTokenSources.TryAdd(query, newTokenSource); try { // 3. 将自定义取消token传入服务层 return await _userService.GetUsersAsync(query, newTokenSource.Token); } catch (OperationCanceledException) { // 可在此处理取消后的逻辑,比如返回空集合或提示信息 return Enumerable.Empty<string>(); } finally { // 4. 请求结束后清理当前取消源,防止内存泄漏 _queryTokenSources.TryRemove(query, out _); newTokenSource.Dispose(); } } }
2. 服务层适配
确保服务方法支持接收CancellationToken,并在内部异步操作中传入该token:
public class UserService : IUserService { public async Task<IEnumerable<string>> GetUsersAsync(string query, CancellationToken cancellationToken) { // 示例:模拟耗时异步操作,实际业务中要把token传给底层操作(如EF Core查询、Http请求等) await Task.Delay(3000, cancellationToken); // 实际业务逻辑示例: // return await _dbContext.Users.Where(u => u.Name.Contains(query)).ToListAsync(cancellationToken); return new List<string> { "用户1", "用户2", "用户3" }; } }
关键细节说明
- 线程安全:必须用
ConcurrentDictionary管理取消源,避免多请求并发操作时出现线程冲突。 - 双重取消触发:通过
CreateLinkedTokenSource把框架的HttpContext.RequestAborted(客户端断开的token)和自定义token绑定,这样不管是新请求触发取消,还是客户端主动断开,都会终止当前操作。 - 内存泄漏预防:请求结束后从字典移除取消源并调用
Dispose;也可以添加定时任务,清理超时未处理的取消源。 - 用户级取消扩展:如果需要按用户区分(仅取消当前用户的旧请求),可以把字典的key改成
$"{userId}_{query}",userId从User.Claims中获取。
内容的提问来源于stack exchange,提问作者Michael
相关产品推荐
相关产品推荐

