Tokio任务内嵌套spawn子任务未按预期执行问题排查
代码错误说明
你的代码违反了Tokio异步运行时的协作式调度规则,存在两个核心问题:
- 异步上下文中误用阻塞式线程睡眠
你在asfunc中使用的std::thread::sleep是同步阻塞调用,会直接冻结当前操作系统线程,不会向Tokio调度器交还执行权。Tokio的调度逻辑要求异步代码在等待时间、IO等事件时主动让出线程资源,否则同线程上挂载的其他任务会被完全饿死,始终拿不到执行机会。 - 异步上下文中误用同步阻塞通道
你使用的std::sync::mpsc是标准库提供的同步通道,它的recv()方法同样是阻塞调用:调用后会一直卡住当前线程直到收到消息,完全不会让出执行权。你在run_test末尾和wrapper函数中都直接调用了阻塞的recv(),进一步加剧了线程阻塞问题。
你只能看到one的输出,本质是调度器先调度了关联one的任务链,阻塞的睡眠、收消息逻辑占住了工作线程,wrapper相关的任务自始至终没有拿到执行资源。
修正方案
按照异步运行时的规范替换对应组件即可:
- 用Tokio提供的异步睡眠
tokio::time::sleep替换std::thread::sleep,睡眠等待时会主动让出执行权 - 用Tokio提供的异步通道
tokio::sync::mpsc替换标准库同步通道,收消息时使用异步的recv()方法,不会阻塞工作线程
修正后的th_test.rs完整代码如下:
use tokio::sync::mpsc::Sender; use tokio::time; async fn wrapper() { // 替换为tokio异步通道,设置合理的通道缓冲区大小 let (tx, mut rx) = tokio::sync::mpsc::channel::<String>(10); tokio::task::spawn(async move { asfunc(tx,"two".to_string()).await; }); // 调用异步recv方法收消息 while let Some(msg) = rx.recv().await { println!("[wrapper] {:?}",msg); } } async fn asfunc(tx: Sender<String>, name: String) { loop { // 替换为tokio异步睡眠 time::sleep(time::Duration::from_secs(1)).await; let now = time::Instant::now(); // 异步通道的send也需要await tx.send(format!("[{}] {:?}",name, now)).await.unwrap(); } } pub async fn run_test() { // 替换为tokio异步通道 let (tx, mut rx) = tokio::sync::mpsc::channel::<String>(10); tokio::task::spawn(async move { asfunc(tx,"one".to_string()).await; }); tokio::task::spawn(async move { wrapper().await; }); // 调用异步recv方法收消息 while let Some(msg) = rx.recv().await { println!("[asfunc] {:?}",msg); } }
main.rs不需要修改,保持原有结构即可。运行后你会看到one和two的日志按1秒间隔交替输出,符合预期。
额外提示:如果确实需要在异步代码中调用无法替换的阻塞逻辑(比如读取同步文件、执行CPU密集型计算),必须用
tokio::task::spawn_blocking把阻塞逻辑放到专门的阻塞线程池中执行,避免卡死Tokio的异步工作线程。
内容的提问来源于stack exchange,提问作者Peter K.
相关产品推荐
相关产品推荐

