【问题标题】:Unexpected tokio::task::spawn_blocking behavior意外的 tokio::task::spawn_blocking 行为
【发布时间】:2021-06-16 19:31:33
【问题描述】:

我正在试验 tokio 的 tokio::spawntokio::task::spawn,结果我不明白后者的行为。

当我运行以下代码时:

#[tokio::main]
pub async fn main() {
    // I'm spawning one block of functions
    let h = tokio::task::spawn_blocking(move || {
        block_one();
    });

    // and another block of functions
    let h2 = tokio::spawn(async move {
        block_two().await;
    });

    // then I collect the handles
    h.await.unwrap();
    h2.await.unwrap();
}

#[tokio::main] //needed as this block is not treated as syncronous by main
pub async fn block_one() {
    let mut handles = vec![];

    for i in 1..10 {
        let h = tokio::spawn(async move {
            println!("Starting func #{}", i);
            i_take_random_time().await;
            println!("Ending func #{}", i);
        });
        handles.push(h);
    }

    for h in handles {
        h.await.unwrap();
    }
}

pub async fn block_two() {
    let mut handles = vec![];

    for i in 10001..10010 {
        let h = tokio::spawn(async move {
            println!("Starting func #{}", i);
            i_take_random_time().await;
            println!("Ending func #{}", i);
        });
        handles.push(h);
    }

    for h in handles {
        h.await.unwrap();
    }
}

我的期望是第一个功能块将完全运行 - 只有第二个功能块才会运行。这就是我对“spawn_blocking”的理解——它会阻止进一步的执行,直到其中的任何内容都完成为止。

我实际上得到的是第二个函数块首先开始(全部,全部 10 个) - 只有在第一个块开始时。所以这与我的预期完全相反。

更令人困惑的是,当我修改上面的代码以使两个块都具有 spawn_blocking 时 - 所有 20 个函数一起开始,就好像两个块都是一个大异步循环的一部分。再次不是我所期望的 - 我认为第一个块会运行,在它完成之前阻塞,然后第二个会运行。

有人可以帮我破译发生了什么吗?

this repo 中提供了重现上述 2 个场景的完整代码。

  • 场景 5 = 我描述的第一个案例
  • 场景 6 = 我描述的第二种情况

注意:这里有两个级别的异步:BETWEEN 块和 WITHIN 块。希望有助于避免任何混淆。

【问题讨论】:

  • 我还没有读完你的整个问题,但你几乎肯定不想用#[tokio::main] 注释block_one。这通常只在您的可执行文件的实际main 函数上完成,因为它为您创建了一个运行时。您可能有(至少)两个运行时,这可能解释了一些不确定的行为。
  • #[tokio::main] 的使用确实不是你想要的,因为它会产生一个新的运行时和一大堆线程,但这不是造成混乱的原因。

标签: rust rust-tokio


【解决方案1】:

听起来你希望spawn_blocking 阻止其他东西运行,但它的目的恰恰相反。 spawn_blocking 的目的是避免阻止其他东西运行。

使用spawn_blocking 的主要地方是用于其他情况下block the thread 的操作,因为它们使用了诸如std::net 之类的非异步操作。它通过将它们卸载到单独的线程池来实现这一点。该名称源于您正在生成一个阻塞操作,以便它可以在其他地方运行。

要等待第一个块完成,您可以这样做:

#[tokio::main]
pub async fn main() {
    // I'm spawning one block of functions
    let h = tokio::task::spawn_blocking(move || {
        block_one();
    });

    // wait for the first block
    h.await.unwrap();

    // then spawn another block of functions
    let h2 = tokio::spawn(async move {
        block_two().await;
    });

    h2.await.unwrap();
}

请注意,在 spawn_blocking 中立即使用 #[tokio::main](或 block_on)很少是您想要的。只需使用tokio::spawn 生成一个普通任务。

【讨论】:

  • 现在点击。谢谢爱丽丝。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2022-06-19
  • 1970-01-01
  • 1970-01-01
  • 2020-10-04
  • 2016-07-16
  • 2016-05-10
  • 2020-07-23
相关资源
最近更新 更多