Async rust misunderstanding

Hello Everyone :slightly_smiling_face:

I'm trying to get a better understanding on async rust with futures but there are severals things that does not make sense to me.
async is supposed to be used to run code asynchronously, making us able to run code faster.

I used this example from the book and tried modifying it a little bit, but the result is not at all what I expected.

The code

use std::thread;
use std::time::{Duration, Instant};
use futures::executor::block_on;

fn main() {
    let start = Instant::now();
    block_on(async_main());
    let duration = start.elapsed();
    println!("Time elapsed is: {:?}", duration);
}

async fn async_main() {
    let f1 = learn_and_sing();
    let f2 = dance();
    println!("ready to execute");
    futures::join!(f1, f2);
}

async fn dance(){
    thread::sleep(Duration::from_secs(5));
}

async fn learn_and_sing() {
    let song = learn_song().await;
    sing_song(song).await
}

async fn sing_song(song: String) {
    println!("{}", song);
    thread::sleep(Duration::from_secs(2));
}

async fn learn_song() -> String {
    thread::sleep(Duration::from_secs(3));
    return "I will survive!".to_string()
}

I would supposed this to be executed in about 5s.

In fact learn_song and sing_song are executed synchronously, and dance asynchronously.

but instead I am having this output,

ready to execute
I will survive
Time elapsed is: 10.012061291s

Can someone help me figuring out what is the issue here ?

You're blocking the runtime by thread::sleep(), causing them to be run one after the other. You need a runtime like tokio, and using tokio::time::sleep().

1 Like

The block_on function in futures just runs the future locally on the current thread. You'll want to use something like tokio to do parallel work.

You would also really want to use tokio's sleep type to avoid wasting a runtime thread. On my machine I don't need to make that change to get your expected behavior with tokio as long as I use tokio::spawn (or another runtime's equivalent) on the futures you want to run in parallel. But on a CPU with very few cores you might still see longer than you expect if you use thread::sleep().

You should also be able to use tokio::join if you use tokio's sleep type, but for illustrative purposes I left them as thread::sleep here

use std::thread;
use std::time::{Duration, Instant};

// Set up the tokio runtime with the defaults and run the main function as a future
#[tokio::main]
async fn main() {
    let start = Instant::now();
    async_main().await;
    let duration = start.elapsed();
    println!("Time elapsed is: {:?}", duration);
}

async fn async_main() {
    // Spawn the tasks so they can run on another thread
    let f1 = tokio::spawn(learn_and_sing());
    let f2 = tokio::spawn(dance());
    println!("ready to execute");
    let _ = tokio::join!(f1, f2);
}

async fn dance() {
    thread::sleep(Duration::from_secs(5));
}

async fn learn_and_sing() {
    let song = learn_song().await;
    sing_song(song).await
}

async fn sing_song(song: String) {
    println!("{}", song);
    thread::sleep(Duration::from_secs(2));
}

async fn learn_song() -> String {
    thread::sleep(Duration::from_secs(3));
    return "I will survive!".to_string();
}

If you're familiar with another language with async/await like JavaScript it can be a bit confusing learning async in Rust because they have very different models of how Futures (Promises in JS) work. In JS if you call an async function but don't await it, it still runs without interfering with the current function. In Rust the future is responsible for actually running the async function, and nothing happens until the future is awaited (or polled by a runtime).

1 Like

I have a whole blog post about this:

4 Likes

You guys are amazing.

Thank you so much for this clear explanation

your blog post is incredibly helpful :slightly_smiling_face:

Thanks!

This topic was automatically closed 90 days after the last reply. We invite you to open a new topic if you have further questions or comments.