为什么tokio::try_join!在spawn任务返回Err时未触发错误分支且未取消其他任务
你混淆了两层
Result的层级,这是所有异常表现的根源tokio::spawn返回的JoinHandle<T>,本身await的返回值是Result<T, JoinError>,这个Result的Err分支仅对应两种情况:任务运行中panic、任务被运行时主动取消。
你代码里spawn的任务返回的是Result<i32, ()>,所以h1.await的实际结果是Ok(Err(()))——对于try_join!来说,它只判断传入Future的输出是不是Err,这里外层是Ok,所以try_join!认为该分支执行成功,会继续等所有分支完成,自然也不会触发取消逻辑。
你注释掉除以零的代码时,任务panic,h1.await返回Err(JoinError::Panic(_)),刚好符合try_join!对Err分支的判断,所以会提前返回,这就是两种表现不一致的原因。其次是spawn生成的独立任务的生命周期特性
没有用spawn的时候,你传给try_join!的是两个直接的async块Future,这两个Future的执行完全由try_join!调度:只要有一个分支返回Err,try_join!会直接drop掉另一个还没完成的Future,而Rust的async Future被drop就会终止执行,所以符合你预期的取消效果。
但tokio::spawn生成的是运行时独立调度的任务,它的生命周期和持有JoinHandle的代码无关:默认情况下,就算JoinHandle被drop,任务依然会在后台执行完毕。所以哪怕try_join!提前返回drop了h2的JoinHandle,h2也不会被取消。
你不需要放弃并行能力,只要做两处调整即可:
- 给每个
JoinHandle开启abort_on_drop配置,开启后只要JoinHandle被drop,对应的spawn任务就会被主动终止 - 把传入
try_join!的Future做一层包装,把两层Result合并,让任务内部返回的业务Err也能被try_join!识别
修改后的示例代码:
use tokio::time::{sleep, Duration}; #[tokio::main] async fn main() { let h1 = tokio::spawn(async { sleep(Duration::from_millis(100)).await; let v: Result<i32, ()> = Err(()); v }).abort_on_drop(); // 开启drop时自动终止任务 let h2 = tokio::spawn(async { sleep(Duration::from_millis(500)).await; println!("h2 didn't get canceled"); let v: Result<i32, ()> = Ok(2); v }).abort_on_drop(); // 两层? 分别透传JoinError和业务Err,触发try_join的错误提前返回逻辑 let join_fut1 = async { h1.await?? }; let join_fut2 = async { h2.await?? }; match tokio::try_join!(join_fut1, join_fut2) { Ok((first, second)) => { println!("try_join was successful, got {:?} and {:?}", first, second); } Err(err) => { println!("try_join had an error: {:?}", err); } } }
运行该代码后h2不会再打印执行日志,符合你预期的错误时取消效果。
内容的提问来源于stack exchange,提问作者jgpaiva

