外观
第 6 节:tokio——异步深入
一句话:第 16 章认识了 join!(同时等全部)。真实项目里,tokio 还有三个高频武器:spawn(任务丢后台)、select!(等最快的)、timeout(等不及就放弃)。
安装
bash
cargo add tokio --features rt-multi-thread,macros,timespawn:把任务丢到后台 ⭐
rust
use tokio::time::{sleep, Duration};
#[tokio::main]
async fn main() {
let handle = tokio::spawn(async {
println!("后台任务开工");
sleep(Duration::from_millis(300)).await;
println!("后台任务完工");
42
});
// ……主任务继续干别的……
let result = handle.await.expect("任务崩溃了");
println!("后台任务返回:{}", result);
}tokio::spawn(异步块):把任务丢给运行时,主任务不等它handle.await:想要结果时再等(和join!不同,spawn是"先放走,后收回")- 返回值:
JoinHandle<T>的await给Result<T, JoinError>——任务内部 panic 会在这里暴露,别直接 unwrap,要处理 'static要求:spawn 的任务必须拥有自己的数据(第 15 章move精神)——借用外面的变量,编译器会拦
select!:等最快的那个 ⭐
rust
use tokio::time::{sleep, Duration};
async fn fetch_page(name: &str, seconds: u64) -> String {
sleep(Duration::from_secs(seconds)).await;
format!("{} 的内容", name)
}
tokio::select! {
result = fetch_page("页面A", 3) => println!("A 先到:{}", result),
result = fetch_page("页面B", 1) => println!("B 先到:{}", result),
}join! 等全部,select! 等最快:第一个完成的 future 胜出,其他直接放弃。
经典场景:请求"多条路,哪条快用哪条"(双域名容灾、缓存 vs 直查)、"等待事件或超时"。
timeout:等不及就放弃 ⭐
rust
use std::time::Duration;
use tokio::time::timeout;
let slow = fetch_page("慢页面", 5);
match timeout(Duration::from_secs(2), slow).await {
Ok(result) => println!("拿到了:{}", result),
Err(_) => println!("等太久了,放弃!"),
}timeout(时长, future):future 没在时限内完成 → 返回Err(Elapsed)- 防止"永远等不到":网络请求、用户输入、跨进程等待,一律套
timeout——真实项目的保命符 - 返回的
Err里没有原 future 的结果——它被取消了(第 16 章:Future 可以"放弃")
常见误用与坑
- 阻塞操作混进 async:
std::thread::sleep、大文件读取、CPU 密集循环——会卡住整个运行时线程(所有任务一起等)。async 世界里用tokio::time::sleep;阻塞活交给tokio::task::spawn_blocking spawn的任务 panic:错误在handle.await时以Err出现——记录日志,别让后台任务炸穿主流程select!会取消输掉的 future:如果输掉的 future 有副作用(写了半截文件),要用"取消安全"的设计- 嵌套运行时:
#[tokio::main]里再建Runtime会 panic——一个程序一个运行时 - features 没开全:
sleep要time、spawn要rt、macros要#[tokio::main]——报"找不到"先查 features