Tokio 取消任务:异步代码不能只会 spawn

发布时间:2026/7/3 2:02:02
Tokio 取消任务:异步代码不能只会 spawn Tokio 取消任务异步代码不能只会 spawn一、能并发启动不代表能正确停止学习 Tokio 时tokio::spawn很容易带来成就感任务可以并发跑起来程序看起来很像生产工具。但真正写 CLI、服务或 Agent 工具时任务能否被取消同样重要。用户按 CtrlC、请求超时、上游失败、配置变更都需要让后台任务有序停止。如果只会 spawn不会取消程序会出现悬挂请求、文件句柄不释放、进度条卡住和退出不干净等问题。异步编程不是把同步代码丢进任务里而是设计任务生命周期。什么时候开始什么时候停止失败如何传播都要清楚。我最早写一个文件批量处理工具时遇到过这样一个问题。用户拖了 200 个文件进去跑到一半发现选错了目录按 CtrlC 想重来。但程序没停后台任务还在逐个打开文件写日志。终端看起来死了实际上 CPU 还在跑。最后只能 kill -9丢了一半中间文件。就是那一次我第一次意识到异步任务不能只会启动不会叫停。二、任务生命周期启动、等待、取消、清理flowchart TD A[启动任务] -- B[执行异步操作] B -- C{完成或取消} C --|完成| D[返回结果] C --|取消| E[释放资源] E -- F[通知调用方]Tokio 中常见取消方式包括select!等待取消信号、使用JoinHandle::abort、通过 channel 通知任务退出或使用CancellationToken。不同方式适合不同场景。强行 abort 简单但清理机会少协作式取消更优雅但需要任务内部定期检查信号。对于网络请求、文件处理和模型流式输出协作式取消更可控。任务可以在安全点停止关闭输出、刷新日志、释放临时文件。系统级工具写到后面优雅退出会比想象中重要。三、代码示例用 select 等待取消信号下面是一个简化示例任务同时等待工作完成和取消通知。use tokio::sync::watch; use tokio::time::{sleep, Duration}; async fn worker(mut shutdown: watch::Receiverbool) { loop { tokio::select! { _ shutdown.changed() { if *shutdown.borrow() { break; } } _ sleep(Duration::from_millis(200)) { println!(working...); } } } println!(worker stopped); }这个例子很小但表达了一个重要习惯任务不是无限跑它应该知道何时退出。真实项目中可以把shutdown传给文件扫描、HTTP 流读取和插件执行等模块。这样顶层收到 CtrlC 后能把退出信号传下去。如果使用JoinHandle::abort要意识到任务可能在任意 await 点停止。对于需要写完临时文件、提交状态或释放锁的任务最好使用协作式取消。能温柔停下的就别硬拔电源。生产环境实战经验在做文件扫描工具时我遇到过一个坑。扫描任务用JoinHandle::abort取消后文件句柄没有释放。原因是底层调用的第三方库在 Drop 实现里才关闭句柄但 abort 不会执行 Drop。后来改成了扫描循环里定期检查CancellationToken并且在退出路径显式 flush 缓冲区。改动不大但文件句柄泄漏导致的文件被占用错误再也没出现过。四、错误传播后台任务失败不能沉默另一个常见坑是 spawn 后不管JoinHandle。任务内部 panic 或返回错误主程序完全不知道。对于重要任务应收集 handle并在退出前等待结果。后台任务失败应该记录日志必要时让主流程失败。超时也属于取消。可以用tokio::time::timeout包住外部请求但超时后要确认底层任务是否真的停止。某些库在 future 被 drop 后会取消请求某些场景还需要额外关闭资源。不要以为 timeout 返回错误就万事大吉。一个真实的失败案例有一次我在异步下载工具里用timeout包住 HTTP 请求超时后只打印了日志没调用连接的close()。结果底层连接池里积了十几个假连接后续所有请求都排在它们后面等待。监控上看 QPS 降到个位数排查了两天才定位到。从那以后超时路径我都会显式释放资源尤其是网络连接和文件描述符。写 AI CLI 时流式响应尤其需要取消。用户按 CtrlC 后应停止读取网络流恢复终端状态输出简短提示。否则终端体验会很难受。异步代码的体面往往体现在退出路径上。五、总结Tokio 异步编程不能只会spawn还要设计任务取消、资源清理和错误传播。协作式取消、超时控制、JoinHandle 管理和 CtrlC 处理是系统级工具的基本功。任务能开始也要能停下。