← 中文资源

简体中文版本

基于 Notify 的 Tokio 优雅关闭

用 tokio::sync::Notify 广播关闭信号,工作循环在 select 中响应并退出,随后等待清理并 join 所有任务再结束进程。

阅读英文原文

功能说明

示例用 tokio::sync::Notify 广播关闭信号,三个工作循环在 tokio::select! 中同时等待通知和 25 毫秒的定时 tick。主任务运行 60 毫秒后调用 notify_waiters(),所有正在等待的工作者被唤醒,跳出循环,执行 20 毫秒的清理,然后由 JoinSet 逐个 join,最后打印关闭完成。

关键代码

tokio::select! {
    _ = shutdown.notified() => {
        println!("worker {worker} draining");
        break;
    }
    _ = sleep(Duration::from_millis(25)) => {
        println!("worker {worker} tick");
    }
}
...
shutdown.notify_waiters();
while let Some(result) = tasks.join_next().await {
    result.expect("worker panicked");
}

notify_waiters 只唤醒当前已注册的等待者,因此必须在工作循环已经开始轮询之后再调用。

使用注意

  • Notify 不保存通知状态;如果某个工作者在通知之后才首次轮询,它会错过信号并继续运行。
  • 示例用固定 60 毫秒确保所有工作者已进入 select,生产环境应结合启动屏障或 watch 通道保存状态。
  • 清理阶段仍是异步的,主任务通过 join_next 等待它完成,不会提前退出。
  • 任务 panic 会在 join_next 返回 Err,示例用 expect 让问题显式暴露。
  • 真实服务还应处理强制超时,避免某个任务卡住导致进程无法退出。

验证方式

运行示例会看到多次 tick,随后三个工作者分别打印 draining 与 stopped,最后输出 shutdown complete。把主任务睡眠改短可能让部分工作者错过通知,可据此理解 Notify 不保存状态的限制。