I assume that your get_player
function takes one second because it waits for a network interaction, and not because some computation takes that long. If it's compute-bound instead, asynchronism is the wrong approach and you want to go with parallelism instead.
Further, I assume that the function signature of get_player
is actually async fn get_player(&self, name: String, i: Instant) -> Option<Player>
instead, because otherwise none of your main
code samples would make any sense. Although I'm confused why it would be &self
and not &mut self
.
With those assumptions, I tried to reproduce your minimal reproducible example:
use std::time::{Duration, Instant};
#[derive(Debug)]
struct Player {
name: String,
}
struct Client {}
impl Client {
async fn init(&self) {}
async fn get_player(&self, name: String, _now: Instant) -> Option<Player> {
// Dummy code that simulates a delay of 1 second
tokio::time::sleep(Duration::from_millis(1000)).await;
Some(Player { name })
}
}
static client: Client = Client {};
#[tokio::main]
async fn main() {
let begin = Instant::now();
client.init().await;
for i in 0..10 {
let now = Instant::now();
let player = client
.get_player(format!("Player #{}", i), now)
.await
.expect("panic");
println!(
"[{} ms] Retreived player: {:?}",
begin.elapsed().as_millis(),
player.name
);
}
}
[1002 ms] Retreived player: "Player #0"
[2004 ms] Retreived player: "Player #1"
[3005 ms] Retreived player: "Player #2"
[4008 ms] Retreived player: "Player #3"
[5010 ms] Retreived player: "Player #4"
[6011 ms] Retreived player: "Player #5"
[7013 ms] Retreived player: "Player #6"
[8014 ms] Retreived player: "Player #7"
[9016 ms] Retreived player: "Player #8"
[10018 ms] Retreived player: "Player #9"
This is based on your last main
example. As you can see, it takes 10 seconds to retrieve all players, because they all run in sequence.
Now let's run them all asynchronously. The problem here is joining them all simultaneously. Tokio sadly doesn't offer an easy way for that; you could tokio::spawn
all of them, collect the JoinHandle
s and then join them one by one. The crate futures
, however, offers exactly what you want:
use std::time::{Duration, Instant};
#[derive(Debug)]
struct Player {
name: String,
}
struct Client {}
impl Client {
async fn init(&self) {}
async fn get_player(&self, name: String, _now: Instant) -> Option<Player> {
// Dummy code her that simulates a delay of 1 second
tokio::time::sleep(Duration::from_millis(1000)).await;
Some(Player { name })
}
}
static client: Client = Client {};
#[tokio::main]
async fn main() {
let begin = Instant::now();
client.init().await;
let get_player_futures = (0..10).into_iter().map(|i| async move {
let now = Instant::now();
let player = client
.get_player(format!("Player #{}", i), now)
.await
.expect("panic");
println!(
"[{} ms] Retreived player: {:?}",
begin.elapsed().as_millis(),
player.name
);
});
futures::future::join_all(get_player_futures).await;
}
[1002 ms] Retreived player: "Player #0"
[1002 ms] Retreived player: "Player #1"
[1002 ms] Retreived player: "Player #2"
[1002 ms] Retreived player: "Player #3"
[1002 ms] Retreived player: "Player #4"
[1002 ms] Retreived player: "Player #5"
[1002 ms] Retreived player: "Player #6"
[1002 ms] Retreived player: "Player #7"
[1003 ms] Retreived player: "Player #8"
[1003 ms] Retreived player: "Player #9"
As you can see, the entire program only took one second, and all of them got retrieved simultaneously.
get_player_futures
here is an iterator over all the futures that need to be awaited for in order to retrieve the players. futures::future::join_all
then awaits all of them simultaneously. You can even use join_all
's return value to retrieve the values of the futures, but we don't use that here.
I hope that helped somehow; it was hard to create an answer as parts of your question were incoherent.