调用Cosmos时CancellationToken未触发取消的问题排查
问题
我无法理解为何数据库请求未被取消,已知该请求耗时约3.5秒。若在发起数据库请求前暂停代码,能触发catch块,但我希望当请求耗时超过100ms时自动取消(实际确实超时)。
代码如下:
public async Task<IEnumerable<Foo>> GetDataFromCosmos(string partition) { var partitionKey = new PartitionKey(partition); CancellationTokenSource s = new CancellationTokenSource(100);//<--100 ms s.Token.ThrowIfCancellationRequested(); sw.Reset(); try { var result = await _dbContext.GetItemsInPartitionAsStreamsAsync<Foo>(CollectionName, partitionKey,s.Token); sw.Stop(); if (sw.ElapsedMilliseconds > 3500) _logger.LogWarning( $"{nameof(GetDataFromCosmos)} data is taking a long time to return: {sw.ElapsedMilliseconds} ms"); return result; } catch (OperationCanceledException exp) { _logger.LogError("timed out"); throw; } catch (Exception e) { Console.WriteLine(e); throw; } }
结果持续显示:
GetDataFromCosmos data is taking a long time to return: 7550 ms
我忽略了什么?
分析与解决
取消令牌未被目标方法正确响应
你传递的CancellationToken要生效,前提是GetItemsInPartitionAsStreamsAsync方法内部会主动检查令牌状态,或者将令牌传递给底层的异步IO操作。如果该方法只是接收令牌但未实际使用,那么即使超时时间到了,数据库请求也不会被中断。ThrowIfCancellationRequested()调用时机无效
创建CancellationTokenSource后立刻调用s.Token.ThrowIfCancellationRequested(),此时100ms超时还未触发,这个调用完全没有意义,不会触发任何取消逻辑。验证令牌本身是否正常工作
可以先做个简单测试:在调用数据库方法前添加await Task.Delay(150, s.Token),如果能触发OperationCanceledException,说明令牌超时逻辑正常,问题就出在数据库方法不支持取消;如果没触发,检查CancellationTokenSource的创建是否有问题。
解决建议:
- 检查
GetItemsInPartitionAsStreamsAsync的实现,确保它在执行过程中会周期性检查CancellationToken,比如在读取流数据的每个阶段调用token.ThrowIfCancellationRequested(),或者将令牌传递给底层的异步读取方法。 - 移除创建令牌后多余的
s.Token.ThrowIfCancellationRequested()调用。 - 如果该数据库方法本身不支持取消,考虑在外部通过其他方式实现超时中断(比如使用
Task.WhenAny结合延迟任务),示例如下:
var dbTask = _dbContext.GetItemsInPartitionAsStreamsAsync<Foo>(CollectionName, partitionKey); var timeoutTask = Task.Delay(100); var completedTask = await Task.WhenAny(dbTask, timeoutTask); if (completedTask == timeoutTask) { _logger.LogError("timed out"); throw new OperationCanceledException("请求超时"); } var result = await dbTask;
内容的提问来源于stack exchange,提问作者macm
相关产品推荐
相关产品推荐

