tokioでタスクをたくさん作って待つ(ド級初心者)

2 min read読了の目安(約2300字

永遠の初級者を自覚しています。
最近なんでかんでも、asyncやらawaitで非同期プログラミングを意識させられます。

それはそれとして、Rustでたくさんタスク(スレッド?)を作って非同期で実行させるのはどうするのか?
みたいなド級初心者的な疑問があり、なんとなく作ってみました。

やりたいこと

  • 非同期ランタイムとしてはTokioを使用する
  • async fn で作った非同期関数をたくさん同時に実行する。
  • それがわかるようにする

ソースコード

試行錯誤のうえこんなんなりました。

cargo.toml
[package]
name = "rust-multi-thread01"
version = "0.1.0"
authors = ["HOGEHOGE <hogehoge@example.com>"]
edition = "2018"

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
anyhow = "1.0.40"
dotenv = "0.15.0"
futures = "0.3.15"
rand = "0.8.3"
tokio = { version = "1.4.0", features = ["full"] }

main.rs
use anyhow::Error;
use futures::future;
use std::time::Duration;
use tokio::time::sleep;


#[tokio::main]
async fn main() {
    let mut tasks = Vec::new();
    for no in 1..=10 {
        let x = tokio::spawn(test01(no));
        tasks.push(x);
    }
    
    sleep(Duration::from_millis(1000)).await;
    // tasks.into_iter().for_each(|t| { let _ = tokio::runtime::Handle::current().block_on(t); });
    println!("=======================");

    let x = future::join_all(tasks.into_iter()).await;
    println!("------------------------");
}

async fn test01(no: i32) -> Result<(), Error> {
    // 待つ時間計算
    let r = rand::random::<u32>() as u64;
    let max = std::u32::MAX as u64;
    let wait = r * 5000 / max;
    println!("{}:start", no);

    sleep(Duration::from_millis(wait as u64)).await;
    
    println!("{}:end", no);


    Ok(())
}

適当な解説

test01 関数でやっていること

開始時にstart、終了時にendを出力。そして処理の本題は最大5秒までランダムで待つ。

タスクの開始

tokio::spawn に 非同期の関数を渡すだけ。そうするとJoinHandleとかいうものが帰ってきます。
JoinHandleをVecに保存しておき後で使います。

タスクの終了を待つ

future::join_all に イテレーターで JoinHandle 渡してやるといいよ、って何処かで書いてあったのでそうしました。最後に .await 入れて待つのを忘れないように。

あと

tasks.into_iter().for_each(|t| { let _ = tokio::runtime::Handle::current().block_on(t); })

こっちでも同じようなことができました。まぁ join_all の方がすっきりしてよいのでは~?と思っています。

tokio::spawn 直後から処理が動いているか確認する

main関数内で sleep(Duration::from_millis(1000)).await; を入れていますが、spawn されてそのまま非同期の関数が実行されるなら、=======================が表示される前にstartの表示がされるはずです。

出力結果

なんかいい感じにできました(´▽`)

6:start
5:start
7:start
2:start
4:start
8:start
1:start
3:start
10:start
9:start
2:end
5:end
7:end
8:end
=======================
10:end
4:end
6:end
3:end
1:end
9:end
------------------------