選取

選取作業會等到任何一組 Future 準備就緒,再針對該 Future 的結果提供回應。這類似於 JavaScript 中的 Promise.race。在 Python 中,則可與 asyncio.wait(task_set, return_when=asyncio.FIRST_COMPLETED) 比較。

類似於比對陳述式,select! 的主體有多個分支,格式皆為 pattern = future => statement。當 future 準備就緒時,回傳值會由 pattern 解構。接著,statement 執行時會利用產生的變數。statement 結果會成為 select! 巨集的結果。

use tokio::sync::mpsc::{self, Receiver};
use tokio::time::{sleep, Duration};

#[derive(Debug, PartialEq)]
enum Animal {
    Cat { name: String },
    Dog { name: String },
}

async fn first_animal_to_finish_race(
    mut cat_rcv: Receiver<String>,
    mut dog_rcv: Receiver<String>,
) -> Option<Animal> {
    tokio::select! {
        cat_name = cat_rcv.recv() => Some(Animal::Cat { name: cat_name? }),
        dog_name = dog_rcv.recv() => Some(Animal::Dog { name: dog_name? })
    }
}

#[tokio::main]
async fn main() {
    let (cat_sender, cat_receiver) = mpsc::channel(32);
    let (dog_sender, dog_receiver) = mpsc::channel(32);
    tokio::spawn(async move {
        sleep(Duration::from_millis(500)).await;
        cat_sender.send(String::from("Felix")).await.expect("Failed to send cat.");
    });
    tokio::spawn(async move {
        sleep(Duration::from_millis(50)).await;
        dog_sender.send(String::from("Rex")).await.expect("Failed to send dog.");
    });

    let winner = first_animal_to_finish_race(cat_receiver, dog_receiver)
        .await
        .expect("Failed to receive winner");

    println!("Winner is {winner:?}");
}
  • 這個範例是貓狗賽跑。first_animal_to_finish_race 會監聽兩個管道,並挑選先抵達者。狗花了 50 毫秒,因此贏過花費 500 毫秒的貓。

  • 在這個範例中,您可以使用 oneshot 管道,因為管道只應接收一個 send

  • 嘗試為賽跑加上期限,示範選擇各種 Future 的情形。

  • 請注意,select! 會捨棄不相符的分支版本,這會取消 Future。若每次執行 select! 時都會建立新的 Future,就最容易使用這種做法。

    • 替代方法是傳遞 &mut future,而非傳遞 Future 本身,但這可能導致多項問題,詳細討論請見 pinning 投影片。