Learn Rust Coding · レッスン

Futureとタスクを扱う

`Future`トレイトと、非同期ランタイム環境内でタスクがスケジュールおよび管理される仕組みを理解します。

レッスン 3/311 ステップ

「Futureとタスクを扱う」はCoddyKit上の無料Learn Rust Codingレッスンです。 これはレッスン3/3です。 下記で完全なレッスンを無料で読むことができます。その後、ブラウザ内の組み込みコードエディタと24時間対応のAIチューターでハンズオン演習できます。 これはLearn Rust Coding学習パスの一部であり、ウェブとCoddyKitアプリ全体で進捗が同期されます。 Learn Rust Codingコースには全3レッスンが含まれています。

Rust の Future とは

非同期 Rust において、Future は、いつか完了する可能性がある非同期計算を表すトレイトです。まだ準備できていない値を約束するものだと考えるとよいでしょう。

  • 非同期 Rust の中心的な構成要素です。
  • Rust の async fn は、実際には Future トレイトを実装する匿名型を返します。
  • 値はすぐには計算されません。エグゼキューターによって Future が「poll」されたときに計算されます。

Future が処理を進める仕組み

Future は、それだけでは実行されません。エグゼキューター(Tokio ランタイムなど)が、進捗したか、完了したかを確認するために、繰り返し「poll」します。

  • poll されると、Future は Poll::Pending(まだ完了していない)または Poll::Ready(T)(完了し、結果はこれ)を返します。
  • Pending の場合、エグゼキューターは、I/O の完了など関係するイベントが発生した後に、もう一度 poll すべきだと判断します。
  • このポーリングの仕組みにより、1 つのスレッド上で多くの非同期操作を並行して実行できます。

あなたの async コードは Future

async fn を記述すると、Rust はそれを Future トレイトを実装するステートマシンに変換します。実際の計算は、返された Future が poll されたときに初めて開始されます。

簡単な async 関数を見てみましょう。この関数は、エグゼキューターによって await されるまで実行されません。

async fn say_hello() -> String {
    "Hello from a Future!".to_string()
}

#[tokio::main]
async fn main() {
    let future = say_hello(); // This doesn't run the function yet!
    println!("Future created, but not awaited.");
    // To run it, an executor needs to poll it, often via .await
    // let result = future.await;
    // println!("{}", result);
}

Future はタスクになる

Future が非同期計算の定義であるのに対し、タスクは、その Future をエグゼキューターが完了まで進めている実行中のインスタンスです。

  • エグゼキューター(Tokio など)に Future の実行を指示すると、それがタスクにラップされます。
  • エグゼキューターはそのタスクを管理し、処理を進められる状態になるたびに poll します。
  • タスクは、非同期ランタイムがスケジュールして並行実行する作業単位です。

並行性のためにタスクを生成する

Future をほかのコードと並行して実行するには、Tokio ランタイム上に「spawn」します。これにより、ランタイムが管理する新しいタスクが作成されます。

tokio::spawn 関数は Future を受け取り、JoinHandle を返します。このハンドルを使ってタスクの完了を await し、結果を取得できます。

async fn perform_task(id: u8) -> String {
    tokio::time::sleep(tokio::time::Duration::from_millis(100)).await;
    format!("Task {} finished!", id)
}

#[tokio::main]
async fn main() {
    println!("Main started.");
    let handle = tokio::spawn(async {
        perform_task(1).await
    });
    println!("Task 1 spawned.");
    // We will await 'handle' in the next scene to get the result.
    // let result = handle.await.unwrap();
    // println!("{}", result);
}

タスクから結果を取得する

tokio::spawn が返す JoinHandle 自体も Future です。このハンドルを .await すると、生成したタスクの完了を待ち、その戻り値を取得できます。

生成したタスクがパニックした場合、その JoinHandle を await するとエラーが返されます。

async fn perform_task(id: u8) -> String {
    tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
    format!("Task {} finished!", id)
}

#[tokio::main]
async fn main() {
    println!("Main started.");
    let handle = tokio::spawn(async {
        perform_task(1).await
    });
    println!("Task 1 spawned.");
    let result = handle.await.unwrap(); // Await the handle to get the result
    println!("{}", result);
    println!("Main finished.");
}

複数の並行タスク

タスクと tokio::spawn の力は、複数の操作を並行して実行するときに特に発揮されます。Tokio ランタイムは、タスクが実行可能になるたびにタスク間を効率よく切り替えます。

これにより、1 つのスレッド上でもブロックすることなく、プログラムが多くの処理を同時に進められます。

async fn long_task(id: u8) -> String {
    tokio::time::sleep(tokio::time::Duration::from_millis(100 + id as u64 * 50)).await;
    format!("Long task {} done!", id)
}

#[tokio::main]
async fn main() {
    println!("Main started.");
    let handle1 = tokio::spawn(long_task(1));
    let handle2 = tokio::spawn(long_task(2));
    println!("Both tasks spawned.");
    let result1 = handle1.await.unwrap();
    let result2 = handle2.await.unwrap();
    println!("{}\n{}", result1, result2);
    println!("Main finished.");
}

join! ですべてを待機する

複数の Future の完了を同時に待つ必要がある場合、tokio::join! は便利なマクロです。指定されたすべての Future を並行して待機し、その結果をタプルとして返します。

各ハンドルを個別に await する方法に似ていますが、Future の数が固定されている場合は、より簡潔に書けることが多いです。

async fn fetch_data(source: &str) -> String {
    tokio::time::sleep(tokio::time::Duration::from_millis(50)).await;
    format!("Data from {}", source)
}

#[tokio::main]
async fn main() {
    println!("Starting data fetches...");
    let (data_a, data_b) = tokio::join!(
        fetch_data("Server A"),
        fetch_data("Database B")
    );
    println!("Fetched: {}\nFetched: {}", data_a, data_b);
    println!("All fetches complete.");
}

タスクのエラーを処理する

非同期操作も、同期操作と同じように失敗することがあります。Future が成功または失敗を示す Result 型を返すのは一般的です。

JoinHandle を await するときは、タスク自体がパニックする可能性があるため、結果がもう 1 つの Result にラップされていることに注意してください。async fn の戻り値が Result の場合は handle.await?、または handle.await.unwrap() / .expect() をよく使用します。

async fn might_fail(should_fail: bool) -> Result<String, &'static str> {
    tokio::time::sleep(tokio::time::Duration::from_millis(30)).await;
    if should_fail {
        Err("Oops, something went wrong!")
    } else {
        Ok("Operation successful!".to_string())
    }
}

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let handle_ok = tokio::spawn(might_fail(false));
    let handle_err = tokio::spawn(might_fail(true));

    let result_ok = handle_ok.await??; // Await handle, then await inner Result
    println!("{}", result_ok);

    match handle_err.await? { // Await handle, then handle inner Result
        Ok(val) => println!("{}", val),
        Err(e) => eprintln!("Error: {}", e),
    }
    Ok(())
}

Future とタスクを理解する

計算としての Future と、その実行インスタンスとしてのタスクについて学びました。tokio::spawn と JoinHandle について正しい説明はどれでしょうか。

復習:Future とタスク

すばらしいです!Rust の非同期エコシステムにおける Future とタスクの基本概念を理解できました。

  • Future は、最終的に値を生成する非同期計算を表すトレイトです。
  • async fn は、Future トレイトを実装する型へコンパイルされます。
  • タスクは、エグゼキューター(Tokio など)が能動的に管理し、poll する Future のインスタンスです。
  • tokio::spawn は新しいタスクの作成に使用され、JoinHandle を返します。
  • JoinHandle を .await すると、生成したタスクの結果を取得できます。

これらの構成要素は、効率的でノンブロッキングな Rust アプリケーションを記述するために不可欠です。

無料で開始

AI チューターと学ぶ Rust — 無料

ブラウザでリアルコードを書いて実行し、24/7 の AI チューターから瞬時にサポートを受け、ウェブまたはアプリで続きから学習できます。

コース
39
レッスン
144

よくある質問

「Futureとタスクを扱う」レッスンは無料ですか?

はい。「Futureとタスクを扱う」の完全なテキストはこのウェブで無料で読めます。インタラクティブに演習し(組み込みコードエディタと24時間対応のAIチューター)、Learn Rust Codingコースの残りをアンロックするには、CoddyKit PROにアップグレードしてください。 Learn Rust Codingコースには全3レッスンが含まれています。

「Futureとタスクを扱う」で何を学びますか?

`Future`トレイトと、非同期ランタイム環境内でタスクがスケジュールおよび管理される仕組みを理解します。 ブラウザで直接実行するハンズオンコードでLearn Rust Codingを演習し、24時間対応のAIチューターがレッスンを進める中での質問に答えます。

Learn Rust Codingを始めるのに経験は必要ですか?

事前経験は必要ありません。CoddyKitのLearn Rust Codingは初級者から上級者向けに構成されているため、ここから始めるか最初から始めて、自分のペースで進むことができます。 これはレッスン3/3です。

「Futureとタスクを扱う」レッスンにはどのくらい時間がかかりますか?

ほとんどのCoddyKitレッスンは約5~10分かかります。各レッスンはコンパクトでインタラクティブなので、着実に進歩し、ウェブとアプリ全体で正確に前回の場所から再開できます。

このLearn Rust Codingレッスンでコードを書いて実行できますか?

はい。すべてのLearn Rust Codingレッスンに組み込みコードエディタが含まれているため、ブラウザでリアルコードを書いて実行し、即座のAIフィードバックを取得できます。ローカル設定は不要です。

このコースのすべてのレッスン

  1. Async/Await入門
  2. Tokioで非同期アプリケーションを構築する
  3. Futureとタスクを扱う
← Learn Rust Codingに戻る