1

I have read here that futures in Rust do nothing unless they are awaited. However, I tried a more complex example and it is a little unclear why I get a message printed by the 2nd print in this example because task::spawn gives me a JoinHanlde on which I do not do any .await.

Meanwhile, I tried the same example, but with an await above the 2nd print, and now I get printed only the message in the 1st print.

If I wait for all the futures at the end, I get printed both messages, which I understood. My question is why the behaviour in the previous 2 cases.

use futures::stream::{FuturesUnordered, StreamExt};
use futures::TryStreamExt;
use rand::prelude::*;
use std::collections::VecDeque;
use std::sync::Arc;
use tokio::sync::Semaphore;
use tokio::task::JoinHandle;
use tokio::{task, time};

fn candidates() -> Vec<i32> {
    Vec::from([2, 2])
}

async fn produce_event(nanos: u64) -> i32 {
    println!("waiting {}", nanos);
    time::sleep(time::Duration::from_nanos(nanos)).await;
    1
}

async fn f(seconds: i64, semaphore: &Arc<Semaphore>) {
    let mut futures = vec![];
    for (i, j) in (0..1).enumerate() {
        for (i, event) in candidates().into_iter().enumerate() {
            let permit = Arc::clone(semaphore).acquire_owned().await;
            let secs = 500;
            futures.push(task::spawn(async move {
                let _permit = permit;
                produce_event(500); // 2nd example has an .await here
                println!("Event produced at {}", seconds);
            }));
        }
    }
}

#[tokio::main()]
async fn main() {
    let semaphore = Arc::new(Semaphore::new(45000));
    for _ in 0..1 {
        let mut futures: FuturesUnordered<_> = (0..2).map(|moment| f(moment, &semaphore)).collect();
        while let Some(item) = futures.next().await {
            let () = item;
        }
    }
}
kmdreko
  • 42,554
  • 6
  • 57
  • 106
  • Please post your code instead of linking it externally, otherwise this question doesn't have much value for the other people in the future – Finomnis Jun 25 '22 at 18:51
  • I think this is a duplicate of: https://stackoverflow.com/questions/62595857/what-is-the-difference-between-tokiospawnmy-future-await-and-just-my-future – Finomnis Jun 25 '22 at 18:54
  • 3
    The quick answer is: `JoinHandle` only represents the action of *waiting for the task to finish*. The actual task already got started in the background and is not connected to whether or not you await the `JoinHandle`. Further, this allows `tokio` to move the spawned task to a different thread. If you only `.await` a task directly, this gets done in the same thread. Both have their valid usecases. Use normal `.await` whenever possible unless you have a reason to perform a `spawn`. – Finomnis Jun 25 '22 at 18:57
  • 1
    Ok after reading your linked code I think there are several effects mixed up: In your first example, none of the `produce_event` get executed because you don't await them. So it directly prints `Event produced`. In your second example, however, you actually do execute the `produce_event`s. Those go to sleep, and because you never wait for those to actually finish, the `main` function exits and cancels all the `produce_event`s. If you add a `wait` in your main, they get executed: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=c1a59eb16933244f4e7f567cf0a9c8f6 – Finomnis Jun 25 '22 at 19:03
  • 1
    Further, I don't understand your `futures` list, you push all the `JoinHandle`s into it but never do anything with it. Here, if you prevent that `main` exits too early by waiting for the `JoinHandle`s, you also see the messages: https://play.rust-lang.org/?version=stable&mode=debug&edition=2021&gist=f1d11d2c37ea6405ae492ad411bd2757 – Finomnis Jun 25 '22 at 19:04
  • @Finomnis I mentioned at the end that if I do `.await` on every future in `futures` I get the predicted output. I understand now why the first example behaves as it does (all the async block gets executed in another task and in that task, I do not await the `produce_event` which causes the 1st print not to happen). Now the second example is the one that gives me head aches. – Alexandru Placinta Jun 25 '22 at 19:09
  • Did you read my previous message? "Ok after reading ..."; I try to explain exactly that, why your second example behaves the way it does. – Finomnis Jun 25 '22 at 19:11
  • I read it, but I still do not understand why the 2nd print does not show – Alexandru Placinta Jun 25 '22 at 19:15
  • 1
    Because **main exits** and all the tasks get cancelled because they are still inside of the `sleep`. The second `print` gets executed **after** `produce_event` is finished, because you await it. `produce_event` never finishes, because `main` exits before that. – Finomnis Jun 25 '22 at 19:15
  • Oh, my bad. Makes sense now. I had to read also the comment answer from @Masklinn – Alexandru Placinta Jun 25 '22 at 19:17
  • No problem :) sometimes it's hard to see the easy reasons if you looked at it for too long. – Finomnis Jun 25 '22 at 19:19
  • Does this answer your question? [What is the difference between tokio::spawn(my\_future).await and just my\_future.await?](https://stackoverflow.com/questions/62595857/what-is-the-difference-between-tokiospawnmy-future-await-and-just-my-future) – Chayim Friedman Jun 25 '22 at 20:15

1 Answers1

3

However, I tried a more complex example and it is a little unclear why I get a message printed by the 2nd print in this example because task::spawn gives me a JoinHanlde on which I do not do any .await.

You're spawning tasks. A task is a separate thread of execution which can execute concurrently to the current task, and can be scheduled in parallel.

All the JoinHandle does there is wait for that task to end, it doesn't control the task running.

Meanwhile, I tried the same example, but with an await above the 2nd print, and now I get printed only the message in the 1st print.

You spawn a bunch of tasks and make them sleep. Since you don't wait for them to terminate (don't join them) nor is there any sort of sleep in their parent task, once all the tasks have been spawned the loops terminate, you reach the end of the main function and the program terminates.

At this point all the tasks are still sleeping.

Masklinn
  • 34,759
  • 3
  • 38
  • 57