Explain why one is async and the other is not - tokio::task

My main:

fn main() {
        let time_1 = std::time::Instant::now();
        test::test();
        println!("Elapsed time_1: {:.3?}", time_1.elapsed());
    }

and 2 async function:

async fn say_world() {
    std::thread::sleep(std::time::Duration::from_secs(3));
    println!("world");
}
async fn say_world_2() {
    std::thread::sleep(std::time::Duration::from_secs(1));
    println!("world_2");
}

Can someone explain / draw why i have different "elapsed time" ?
3 seconds mean that its work async
4 seconds mean that its work sync

And what is a difference between 2) and 3) ?

#[tokio::main]
pub async fn test() {
    // Calling `say_world()` does not execute the body of `say_world()`.
    let op_1 = tokio::task::spawn(say_world());
    let op_2 = say_world_2();

    // This println! comes first
    println!("hello");

    // Calling `.await` on `op` starts executing `say_world...`.
    op_1.await;
    op_2.await;
}

hello
world
world_2
Elapsed time_1: 4.002s

#[tokio::main]
pub async fn test() {
    // Calling `say_world()` does not execute the body of `say_world()`.
    let op_1 = say_world();
    let op_2 = tokio::task::spawn(say_world_2());

    // This println! comes first
    println!("hello");

    // Calling `.await` on `op` starts executing `say_world...`.
    op_1.await;
    op_2.await;
}

hello
world_2
world
Elapsed time_1: 3.002s

#[tokio::main]
pub async fn test() {
    // Calling `say_world()` does not execute the body of `say_world()`.
    let op_1 = tokio::task::spawn(say_world());
    let op_2 = tokio::task::spawn(say_world_2());

    // This println! comes first
    println!("hello");

    // Calling `.await` on `op` starts executing `say_world...`.
    op_1.await;
    op_2.await;
}

hello
world_2
world
Elapsed time_1: 3.003s

Tokio is able to concurrently run many tasks on a few threads by repeatedly swapping the currently running task on each thread. However, this kind of swapping can only happen at .await points, so code that spends a long time without reaching an .await will prevent other tasks from running.

Tokio docs

When you use std::thread::sleep, that blocks the thread as there is no .await, preventing other things from running. Use delay_for instead.

Thanks for info about "delay_for" but i think this is not a problem here.

If i change 2 async function i have the same result and don't understood why "Elapsed times" are different

async fn say_world() {
    let client = reqwest::Client::new();
    let resp = client
        .get("http://slowwly.robertomurray.co.uk/delay/3000/url/http://www.google.co.uk")
        .header(header::USER_AGENT, "kkoo")
        .send().await;
    println!("world");
}

async fn say_world_2() {
    // std::thread::sleep(std::time::Duration::from_secs(1));
    let client = reqwest::Client::new();
    let resp = client
        .get("http://slowwly.robertomurray.co.uk/delay/1000/url/http://www.google.co.uk")
        .header(header::USER_AGENT, "kkoo")
        .send().await;
    println!("world_2");
}

Okay, let's look at them again assuming you use delay_for rather than sleep.


Case 1.

let op_1 = tokio::task::spawn(say_world());
let op_2 = say_world_2();
println!("hello");
op_1.await;
op_2.await;
Show full code
async fn say_world() {
    tokio::time::delay_for(std::time::Duration::from_secs(3)).await;
    println!("world");
}
async fn say_world_2() {
    tokio::time::delay_for(std::time::Duration::from_secs(1)).await;
    println!("world_2");
}

#[tokio::main]
pub async fn test() {
    let op_1 = tokio::task::spawn(say_world());
    let op_2 = say_world_2();

    println!("hello");

    op_1.await;
    op_2.await;
}

fn main() {
    let time_1 = std::time::Instant::now();
    test();
    println!("Elapsed time_1: {:.3?}", time_1.elapsed());
}
hello
world
world_2
Elapsed time_1: 4.003s

In this case, op_1 gets spawned and starts running immediately. As for op_2, it's an async function so it does not start running until it is awaited.

The op_1.await expression finished after three seconds when the spawned task is done. Then op_2.await is started, which starts executing say_world_2(). This takes another second, hence the total time is four seconds.


Case 2.

let op_1 = say_world();
let op_2 = tokio::task::spawn(say_world_2());
println!("hello");
op_1.await;
op_2.await;
Show full code
async fn say_world() {
    tokio::time::delay_for(std::time::Duration::from_secs(3)).await;
    println!("world");
}
async fn say_world_2() {
    tokio::time::delay_for(std::time::Duration::from_secs(1)).await;
    println!("world_2");
}

#[tokio::main]
pub async fn test() {
    let op_1 = say_world();
    let op_2 = tokio::task::spawn(say_world_2());
    
    println!("hello");
    
    op_1.await;
    op_2.await;
}


fn main() {
    let time_1 = std::time::Instant::now();
    test();
    println!("Elapsed time_2: {:.3?}", time_1.elapsed());
}
hello
world_2
world
Elapsed time_2: 3.002s

In this case, the call to spawn makes say_world_2() start running immediately. The code then goes on to op_1.await, which starts execution when it is awaited. The op_1.await expression takes three seconds, after which it goes on to op_2.await. However op_2 was already started when it was spawned, so it finished two seconds ago, and op_2.await is immediately done. Thus the elapsed time is 3 seconds.


let op_1 = tokio::task::spawn(say_world());
let op_2 = tokio::task::spawn(say_world_2());
println!("hello");
op_1.await;
op_2.await;
Show full code
async fn say_world() {
    tokio::time::delay_for(std::time::Duration::from_secs(3)).await;
    println!("world");
}
async fn say_world_2() {
    tokio::time::delay_for(std::time::Duration::from_secs(1)).await;
    println!("world_2");
}

#[tokio::main]
pub async fn test() {
    let op_1 = tokio::task::spawn(say_world());
    let op_2 = tokio::task::spawn(say_world_2());
    
    println!("hello");
    
    op_1.await;
    op_2.await;
}


fn main() {
    let time_1 = std::time::Instant::now();
    test();
    println!("Elapsed time_3: {:.3?}", time_1.elapsed());
}
hello
world_2
world
Elapsed time_3: 3.003s

Both op_1 and op_2 are started immediately when spawned. Then you wait for op_1 to finish, which it does three seconds later. You then wait for op_2 to finish, but it finished two seconds ago, so you go forward immediately. The total time is 3 seconds.

3 Likes