news 2026/6/10 13:06:48

异步编程---异步取消机制CancellationToken

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
异步编程---异步取消机制CancellationToken

异步代码不加取消机制?犹如开车没有刹车

没有取消机制的异步代码,就像开车没有刹车。表面看似没问题,直到你发现应用仍在疯狂运行、消耗内存、执着地完成早已无人需要的工作。这不是健壮性,而是披着高效外衣的资源浪费。
如果你仍以“没有取消令牌也能运行”为由跳过它们,那你写的就不是健壮的异步代码——你写的是为所欲为的代码。现在可能没问题,但迟早会出事。
如果你曾点过毫无反应的取消按钮,你就会明白:这个话题值得掌握。
(第二部分现已发布)


什么是 CancellationToken?
本质上,CancellationToken 只是一个信号——一个轻量级对象,用于告知异步代码:“该停止了。”但令牌本身并不执行取消操作,它只负责接收信号。
发送信号的是 CancellationTokenSource。你创建源对象,从中获取令牌,并将令牌传递给任何需要支持取消的方法。
基本结构

usingvarcts=newCancellationTokenSource();CancellationTokentoken=cts.Token;// 可以在其他地方取消令牌:cts.Cancel();

可以这样理解:

  • CancellationTokenSource = 控制器
  • CancellationToken = 广播信号
  • 异步方法 = 监听器(收到信号后自愿停止)

如何在异步方法中传递和使用令牌
几乎所有规范的 .NET API 都接受可选 CancellationToken 参数,这意味着你的方法也应该如此。
定义带令牌参数的方法

publicasyncTaskDoWorkAsync(CancellationTokencancellationToken){for(inti=0;i<10;i++){cancellationToken.ThrowIfCancellationRequested();Console.WriteLine($"Working...{i}");awaitTask.Delay(500,cancellationToken);}}

在调用链中传递令牌

usingvarcts=newCancellationTokenSource();Tasktask=DoWorkAsync(cts.Token);// 2秒后取消cts.CancelAfter(TimeSpan.FromSeconds(2));

注意:

  • ThrowIfCancellationRequested() 用于响应取消信号
  • Task.Delay(…) 也支持令牌,因此取消操作可以中断等待
  • 必须将令牌传递给每个可取消操作
  • 判断IsCancellationRequested 用于判断令牌是否被取消
    以下是一个支持取消的简单异步方法:
publicasyncTask<string>DownloadAsync(stringurl,CancellationTokencancellationToken){usingvarhttpClient=newHttpClient();varresponse=awaithttpClient.GetAsync(url,cancellationToken);returnawaitresponse.Content.ReadAsStringAsync();}

传递令牌让被调用方有权选择退出。调用方式如下:

varcts=newCancellationTokenSource(TimeSpan.FromSeconds(5));try{varcontent=awaitDownloadAsync("https://example.com",cts.Token);Console.WriteLine("Download succeeded!");}catch(OperationCanceledException){Console.WriteLine("Download cancelled.");}

必须单独处理 OperationCanceledException,以区分操作失败和取消。

判断IsCancellationRequested
在执行循环任务的时候可以通过IsCancellationRequested判断是否被取消任务来进行退出,这样可以不用抛出异常。

// 4. 主动判断是否取消privateasyncTaskActiveCancellationExample(){usingvarcts=newCancellationTokenSource();CancellationTokentoken=cts.Token;// 5秒触发取消cts.CancelAfter(TimeSpan.FromSeconds(5));// 启动工作任务Task<bool>workTask=DoWorkNoErrorAsync(token);try{varres=awaitworkTask;Console.WriteLine($"工作已完成:{res}");}catch(OperationCanceledException){Console.WriteLine("工作已被取消");}}privateasyncTask<bool>DoWorkNoErrorAsync(CancellationTokencancellationToken){for(inti=0;i<10;i++){// 检查是否已请求取消if(cancellationToken.IsCancellationRequested){// 可选:添加取消前的清理逻辑(如释放资源)returnfalse;}Console.WriteLine($"工作中...{i}");// 捕获Delay期间的取消异常awaitTask.Delay(500);}returntrue;}---

忽略取消机制带来的隐藏缺陷
问题在这里变得严重。忽略取消令牌不仅是错过优化,更是滋生难以复现缺陷的温床:

  1. 僵尸任务
    用户取消了操作,但任务仍在后台运行:写入内存、更新状态,甚至可能随后抛出错误。
  2. 应用关闭延迟
    不可取消的任务会阻塞优雅关闭,迫使你依赖 Environment.Exit(1) 而非干净的退出钩子。
  3. 内存泄漏
    不释放 CancellationTokenSource 或在循环中忽略取消,可能导致内存持有时间远超预期。
  4. 测试无限挂起
    依赖异步操作但无取消机制的测试没有退出策略,CI 流水线可能因小错误卡住 30 分钟。
    忽略取消不仅是懒惰,更是危险。

为何应该使用取消令牌
因为你的代码不是孤立运行的。
异步操作发生在某些生命周期内:

  • 用户离开页面时
  • 后台服务关闭时
  • •UI 发出停止加载数据的信号时
    如果代码忽略取消:
  • 浪费计算资源
  • 迫使调用方使用 Hack 手段(超时、强制终止)
  • 导致测试脆弱性
    如果你正在构建可复用 API,标准更高。使用者期望能传递取消令牌。如果方法签名不支持,会降低代码在实际系统中的可用性。

示例:协作式控制台应用publicstaticasyncTaskMain(){usingvarcts=newCancellationTokenSource();Console.CancelKeyPress+=(s,e)=>{e.Cancel=true;cts.Cancel();};try{awaitDoWorkAsync(cts.Token);Console.WriteLine("Work completed successfully.");}catch(OperationCanceledException){Console.WriteLine("Work was cancelled.");}}staticasyncTaskDoWorkAsync(CancellationTokentoken){for(inti=0;i<10;i++){token.ThrowIfCancellationRequested();Console.WriteLine($"Working...{i}");awaitTask.Delay(1000,token);}}

按 Ctrl+C 即可取消。这是真实场景的行为——应用按指令退出,而不是随心所欲。


忽略取消的代价
许多开发者将 CancellationToken 视为可选参数。他们将其塞入方法签名以满足分析器或库规范,却从不实际使用。这种心态会导致应用无响应、关闭缓慢、CPU 周期浪费,甚至内存泄漏。
理解取消机制不仅是编写防御性代码,更是编写专业代码:尊重用户时间、节约系统资源、在压力下保持可预测行为。
本文奠定了基础:令牌是什么、如何传递、为何重要,以及忘记使用会如何导致微妙而痛苦的缺陷。

实例的代码已经上传到Gitee:https://gitee.com/panwangvie/PwjBlogExamples/blob/master/AsyncCodeDemo/CancellationExamples.cs

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/10 3:38:33

异步编程,相关锁的介绍,SemaphoreSlim 信号量

关于SemaphoreSlim 信号量的使用注意事项 SemaphoreSlim 类 (System.Threading) Wait/Release 成对性&#xff08;try/finally&#xff09;&#xff1b;嵌套 Wait 的死锁问题&#xff1b;必须为 Wait 设置超时&#xff1b;异步场景 WaitAsync 的正确使用&#xff1b;重复Releas…

作者头像 李华
网站建设 2026/6/10 6:34:39

ComfyUI-SeedVR2终极指南:快速实现专业级视频画质提升

ComfyUI-SeedVR2终极指南&#xff1a;快速实现专业级视频画质提升 【免费下载链接】ComfyUI-SeedVR2_VideoUpscaler Non-Official SeedVR2 Vudeo Upscaler for ComfyUI 项目地址: https://gitcode.com/gh_mirrors/co/ComfyUI-SeedVR2_VideoUpscaler 想要将模糊视频瞬间变…

作者头像 李华
网站建设 2026/6/3 7:38:38

Gitnuro完全指南:从安装到精通的跨平台Git管理方案

Gitnuro完全指南&#xff1a;从安装到精通的跨平台Git管理方案 【免费下载链接】Gitnuro A FOSS Git multiplatform client for newbies and pros 项目地址: https://gitcode.com/GitHub_Trending/gi/Gitnuro Gitnuro是一款基于JetBrains Compose和JGit开发的跨平台开源…

作者头像 李华
网站建设 2026/6/9 7:06:48

JUnit4测试顺序控制终极指南:告别随机执行困扰

JUnit4测试顺序控制终极指南&#xff1a;告别随机执行困扰 【免费下载链接】junit4 A programmer-oriented testing framework for Java. 项目地址: https://gitcode.com/gh_mirrors/ju/junit4 你是否曾经遇到过这样的情况&#xff1a;精心编写的测试用例在运行时却像抽…

作者头像 李华
网站建设 2026/6/9 23:30:44

快速掌握Typst数学符号:从入门到精通的终极指南

你是否曾经被复杂的数学公式排版困扰&#xff1f;&#x1f914; 想不想知道如何在Typst中轻松调用各种数学符号&#xff1f;作为新一代标记语言排版系统&#xff0c;Typst凭借其简洁语法和强大功能&#xff0c;正在改变技术文档写作的方式。今天&#xff0c;就让我们一起来探索…

作者头像 李华
网站建设 2026/6/9 18:50:22

AgentScope自定义模型集成:如何突破内部AI服务的技术壁垒

你是否正在为企业的私有AI模型无法融入现有Agent系统而烦恼&#xff1f;当内部开发的智能服务与标准接口存在差异时&#xff0c;传统的集成方案往往显得力不从心。本文将带你深入探索AgentScope自定义模型集成的核心技术&#xff0c;从问题诊断到方案落地&#xff0c;一站式解决…

作者头像 李华