选择

选择操作会等待一组 Future 中的任意一个就绪,并对 Future 产生的结果进行响应。在 JavaScript 中,该操作类似于 Promise.race。在 Python 中,它相当于 asyncio.wait(task_set, return_when=asyncio.FIRST_COMPLETED)

Similar to a match statement, the body of select! has a number of arms, each of the form pattern = future => statement. When a future is ready, its return value is destructured by the pattern. The statement is then run with the resulting variables. The statement result becomes the result of the select! macro.

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 本身,但这可能会导致问题,在本幻灯片的 “固定” 部分进行了详细介绍。