给 .NET 网络调用设定“预算”:用 CancellationTokenSource.CancelAfter 处理超时取消

给 .NET 网络调用设定"预算":用 CancellationTokenSource.CancelAfter 处理超时取消

网络请求没有在预期时间内完成时,调用方往往不希望无限等待。C# 的 CancellationTokenSource.CancelAfter 可以把这种等待上限表达为取消计划:到达指定时间后,它会向关联的取消令牌发出取消信号。Microsoft Learn 的示例把令牌传给 HttpClient.GetAsync 和 HttpContent.ReadAsByteArrayAsync,并在入口处捕获 OperationCanceledException。

先分清:取消信号不是"强行终止"

CancelAfter 的职责是安排取消,而不是替某个任意方法强制收尾。被调用的 API 是否能够及时结束,取决于它是否接收并观察同一个 CancellationToken。因此,下面两步应成对出现:

  1. 用 CancellationTokenSource 生成令牌,并通过 CancelAfter 安排取消;
  2. 把 cts.Token 继续传给支持取消的异步 API。

一个最小的 HTTP 示例

csharp 复制代码
using System.Net.Http;

static async Task<string> GetBodyAsync(Uri uri, TimeSpan timeout)
{
    using var cts = new CancellationTokenSource();
    cts.CancelAfter(timeout);

    try
    {
        using var response = await new HttpClient().GetAsync(uri, cts.Token);
        response.EnsureSuccessStatusCode();
        return await response.Content.ReadAsStringAsync(cts.Token);
    }
    catch (OperationCanceledException) when (cts.IsCancellationRequested)
    {
        throw new TimeoutException($"请求在 {timeout} 内未完成。");
    }
}

这里的 when (cts.IsCancellationRequested) 用于区分这个调用范围内由超时计划触发的取消。是否要转换成 TimeoutException 取决于调用方的错误模型;如果上层需要统一处理取消,也可以保留原始的 OperationCanceledException。

把令牌传到实际 I/O 边界

只在外层调用 CancelAfter、却遗漏向 I/O API 传递令牌,是最常见的失配。Microsoft Learn 的完整示例在循环中调用 ProcessUrlAsync(url, client, s_cts.Token),而该方法又将 token 传给 client.GetAsync(url, token) 与 response.Content.ReadAsByteArrayAsync(token)。这样,下载尚未完成且已到取消时间时,等待中的可取消操作才有机会以取消结束。

异常处理放在调用边界

官方示例在 Main 的 try 块中设置 s_cts.CancelAfter(3500) 并等待工作方法;若取消先发生,则捕获 OperationCanceledException。这种安排把"这一批工作是否超时"的决定集中在入口处,而不需要让每个底层方法各自猜测如何展示或记录超时。

同时要注意,取消与 HTTP 成功不是一回事:GetAsync 成功返回后,仍可能得到非成功状态码,所以示例额外调用 EnsureSuccessStatusCode()。取消逻辑解决的是等待边界;状态码校验仍应按业务需要单独处理。

资源生命周期也属于实现的一部分

Microsoft Learn 的示例在 finally 中释放 CancellationTokenSource。在局部方法中使用 using 能让这一点更直接:请求成功、发生 HTTP 错误或因取消离开方法时,资源都会被释放。对于复用的 HttpClient,则应依据应用的生命周期和既有配置管理,而不是仅为一次请求随意复制全局策略。

落地检查清单

  • 超时上限是否由调用方明确给出,而不是散落的魔法数字?
  • 令牌是否一直传到了数据库、HTTP、文件等真正可等待的 API?
  • 是否在合适的调用边界处理 OperationCanceledException?
  • 是否把取消、非成功 HTTP 状态和其他故障分别记录或映射?

把取消看作协作协议,而不是中断按钮,代码的控制流会更清晰:调用方声明等待预算,底层 API 观察令牌,边界层决定如何向上报告结果。

参考资料

相关推荐
清风儿3291 小时前
一块不会说话的电表,把我们折腾了半个月
后端
预知同行1 小时前
深入解析 MoE 混合专家模型:从 Router 到 All-to-All 的架构全景与生产实践
后端·架构
upala1 小时前
【仓颉语言入门 · 第二篇 第 8 课】
后端
weiwin1232 小时前
MAF 入门(8):多模态实战——让 Agent 看懂图片、处理混合内容
人工智能·后端
弈栈录2 小时前
流式对话接口设计:SSE、WebSocket 与断线重连
后端
Java内核笔记2 小时前
Spring AI 生产级 Chat 接口:6 个开关、13 个坑,其中 2 个开关根本没生效
java·人工智能·后端
是江迪呀2 小时前
向全世界售卖你的产品:做了一款教程引导插件
前端·javascript·后端
WeiXin_DZbishe2 小时前
基于springboot装修风格选择系统的设计与开发-计算机毕设【课程设计】77964
java·vue.js·spring boot·后端·python·php·课程设计
leikooo3 小时前
ARTS 1018: 字符计数判断异位词、局部做对仍会增加维护负担与用 AI 也要理解自己在做什么
后端