Rust中如何等待thread::scope与tokio_scoped内的异步线程?
问题
在Rust开发中,我尝试在thread::scope内部spawn异步线程执行异步函数add_two_num_with_more_number,但线程未被等待,导致计算结果无法打印。改用tokio_scoped库实现时,出现“closure不是future”的编译错误,按照提示添加()后又产生新错误。请问如何正确等待这些异步线程执行完成?
初始代码
use anyhow::{Ok, Result}; use std::sync::Arc; use std::thread; // 非Copy结构体 pub struct MoreNumbers { third_num: u64, fourth_num: u64, } pub async fn add_two_num_with_more_number( num_one: u64, num_two: u64, more_nums: &MoreNumbers, ) -> Result<u64> { check_num(num_one).await?; // 调用另一个返回anyhow::Result的异步函数 check_num(num_two).await?; anyhow::Ok(num_one + num_two + more_nums.third_num + more_nums.fourth_num) } pub async fn check_num(num: u64) -> Result<()> { assert!(num <= u64::MAX /2, "两个大于u64::MAX一半的数相加有溢出风险"); Ok(()) } #[tokio::main] async fn main() -> anyhow::Result<()> { let mut TRIES: u8 = 5; let many_nums: Vec<u64> = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]; let more_nums: MoreNumbers = MoreNumbers { third_num: 12, fourth_num: 13, }; let more_nums_arc: Arc<MoreNumbers> = Arc::new(more_nums); while TRIES > 1 { many_nums.chunks(2).for_each(|vector| { thread::scope(|s| { let more_nums_arc_cloned: Arc<MoreNumbers> = Arc::clone(&more_nums_arc); if let [first, second] = vector { s.spawn(move || async move { // ------ >>>> 注意这里! <<<< let result = add_two_num_with_more_number(*first, *second, &more_nums_arc_cloned) .await?; println!("{}", &result); // ------ >>>> 这里需要打印结果! <<<< anyhow::Ok(()) }); }; }) }); TRIES -= 1; } anyhow::Ok(()) }
初始代码问题
代码可编译,但result无法打印,核心原因是:thread::scope管理的是OS线程,而你在spawn的闭包里仅返回了一个异步future——OS线程不会自动驱动异步任务执行,必须有异步执行器来运行这个future,同时你也没有等待OS线程内的任务完成。
使用tokio_scoped的尝试代码
async fn main() -> anyhow::Result<()> { let mut TRIES: u8 = 5; let many_nums: Vec<u64> = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]; let more_nums: MoreNumbers = MoreNumbers { third_num: 12, fourth_num: 13, }; let more_nums_arc: Arc<MoreNumbers> = Arc::new(more_nums); while TRIES > 1 { tokio_scoped::scope(|s| { many_nums.chunks(2).for_each(|vector| { let more_nums_arc_cloned: Arc<MoreNumbers> = Arc::clone(&more_nums_arc); if let &[first, second] = vector { s.spawn(move |_| async move { // ------ >>>> 注意这里! <<<< let result = add_two_num_with_more_number(first, second, &more_nums_arc_cloned).await?; println!("{}", &result); // ------ >>>> 这里需要打印结果! <<<< anyhow::Ok(()) }); }; }); }); TRIES -= 1; } anyhow::Ok(()) }
编译错误信息
error[E0277]: `[closure@src\main.rs:42:29: 42:37]` is not a future --> src\main.rs:42:29 | 42 | s.spawn(move |_| async move { // ------ >>>> 注意这里! <<<< | _______________________-----_^ | | | | | 此调用引入的约束要求 43 | | let result = 44 | | add_two_num_with_more_number(*first, *second, &more_nums_arc_cloned).await?; 45 | | println!("{}", &result); // ------ >>>> 这里需要打印结果! <<<< 46 | | anyhow::Ok(()) 47 | | }); | |_____________________^ `[closure@src\main.rs:42:29: 42:37]` 不是future | = help: 特征 `std::future::Future` 未为闭包 `[closure@src\main.rs:42:29: 42:37]` 实现 = note: [closure@src\main.rs:42:29: 42:37] 必须是future或实现`IntoFuture`才能被await note: 约束由`tokio_scoped::Scope::<'a>::spawn`引入 --> C:\Users\Jym Chng.DESKTOP-N84UN90\.cargo\registry\src\github.com-1ecc6299db9ec823\tokio-scoped-0.2.0\src\lib.rs:175:12 | 175 | F: Future<Output = ()> + Send + 'a, | ^^^^^^^^^^^^^^^^^^^ `tokio_scoped::Scope::<'a>::spawn`中的此约束要求 help: 使用括号调用此闭包 | 47 | }()); | ++
修改后的错误信息
error[E0618]: 预期函数,找到`impl std::future::Future<Output = std::result::Result<(), anyhow::Error>>` --> src\main.rs:42:38 | 42 | s.spawn(move |_| async move { // ------ >>>> 注意这里! <<<< | ______________________________________^ | |______________________________________| | || 43 | || let result = 44 | || add_two_num_with_more_number(first, second, &more_nums_arc_cloned).await?; 45 | || println!("{}", &result); // ------ >>>> 这里需要打印结果! <<<< 46 | || anyhow::Ok(()) 47 | || }()); | ||_____________________^-- 调用表达式需要函数 | | || | |______________________|help: 考虑在这里使用分号: `;`
解决方案
方案1:使用Tokio自带的scoped任务(推荐)
Tokio 1.21+版本支持scoped spawn,可直接在异步上下文中生成能安全引用当前栈变量的任务,且会自动等待所有任务完成,无需手动管理Arc或线程。
修改后的代码:
use anyhow::{Ok, Result}; use tokio::task; // 非Copy结构体 pub struct MoreNumbers { third_num: u64, fourth_num: u64, } pub async fn add_two_num_with_more_number( num_one: u64, num_two: u64, more_nums: &MoreNumbers, ) -> Result<u64> { check_num(num_one).await?; check_num(num_two).await?; anyhow::Ok(num_one + num_two + more_nums.third_num + more_nums.fourth_num) } pub async fn check_num(num: u64) -> Result<()> { assert!(num <= u64::MAX /2, "两个大于u64::MAX一半的数相加有溢出风险"); Ok(()) } #[tokio::main] async fn main() -> anyhow::Result<()> { let mut TRIES: u8 = 5; let many_nums: Vec<u64> = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]; let more_nums: MoreNumbers = MoreNumbers { third_num: 12, fourth_num: 13, }; while TRIES > 1 { // 使用tokio的scoped任务,自动等待所有子任务完成 task::scope(|s| async { for vector in many_nums.chunks(2) { if let [first, second] = vector { // 直接spawn异步任务,scoped允许安全引用外部栈变量 s.spawn(async move { let result = add_two_num_with_more_number(*first, *second, &more_nums).await?; println!("{}", result); anyhow::Ok(()) }); } } }).await?; TRIES -= 1; } anyhow::Ok(()) }
核心说明:
tokio::task::scope会自动等待所有内部spawn的任务完成,无需手动join- scoped任务通过生命周期约束实现了栈变量的安全引用,无需Arc即可访问外部变量(多任务共享同一变量时仍需Arc+Clone)
- 异步任务由Tokio执行器直接驱动,不会出现任务未执行或未等待的问题
方案2:修复tokio_scoped的使用方式
若坚持使用tokio_scoped,需注意其spawn方法接收的是Future类型,而非带参数的闭包,直接传入异步块即可:
use anyhow::{Ok, Result}; use std::sync::Arc; // 结构体和异步函数部分同初始代码... #[tokio::main] async fn main() -> anyhow::Result<()> { let mut TRIES: u8 = 5; let many_nums: Vec<u64> = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]; let more_nums: MoreNumbers = MoreNumbers { third_num: 12, fourth_num: 13, }; let more_nums_arc: Arc<MoreNumbers> = Arc::new(more_nums); while TRIES > 1 { tokio_scoped::scope(|s| { many_nums.chunks(2).for_each(|vector| { let more_nums_arc_cloned = Arc::clone(&more_nums_arc); if let &[first, second] = vector { // 移除多余的move |_|,直接传入异步future s.spawn(async move { let result = add_two_num_with_more_number(first, second, &more_nums_arc_cloned).await?; println!("{}", result); anyhow::Ok(()) }); }; }); }); TRIES -= 1; } anyhow::Ok(()) }
核心说明:
tokio_scoped::scope::spawn要求参数是Future<Output = ()>,直接传递async move块即可scope会自动等待所有内部任务完成,确保结果能被打印
方案3:手动驱动异步任务(不推荐)
若一定要用thread::scope,需在每个OS线程内启动Tokio runtime来驱动异步任务,并手动等待线程完成:
// 结构体和异步函数部分同初始代码... #[tokio::main] async fn main() -> anyhow::Result<()> { let mut TRIES: u8 = 5; let many_nums: Vec<u64> = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]; let more_nums: MoreNumbers = MoreNumbers { third_num: 12, fourth_num: 13, }; let more_nums_arc: Arc<MoreNumbers> = Arc::new(more_nums); while TRIES > 1 { many_nums.chunks(2).for_each(|vector| { thread::scope(|s| { let more_nums_arc_cloned = Arc::clone(&more_nums_arc); if let [first, second] = vector { let handle = s.spawn(move || { // 在OS线程内启动单线程runtime,驱动异步任务 let rt = tokio::runtime::Runtime::new().unwrap(); rt.block_on(async move { let result = add_two_num_with_more_number(*first, *second, &more_nums_arc_cloned).await?; println!("{}", result); anyhow::Ok(()) }) }); // 等待OS线程完成,确保任务执行完毕 handle.join().unwrap()?; }; }) }); TRIES -= 1; } anyhow::Ok(()) }
核心说明:
- 每个OS线程都需要单独启动Tokio runtime,用
block_on阻塞线程直到异步任务完成 - 调用
handle.join()等待OS线程结束,确保结果能被打印 - 此方式开销较大,不推荐用于生产环境,仅作特殊场景下的备选方案
内容的提问来源于stack exchange,提问作者Jim
相关产品推荐
相关产品推荐

