You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.23 02:02:12