How to handle a vector of async function pointers

Here's a pretty good mental model of how futures work in Rust:

Imagine you had a list of one thousand elements called futures. Each future has a function called poll, which does a little bit of work and then returns quickly. By looping through the list and calling poll on each future, you can do work on all one thousand tasks at once in a single thread.

However, note that this requires the futures to cooperate. If a future spends a long time inside poll, then your thread doesn't have time to go work on the other tasks. In your case you are using thread::sleep, which has exactly this behaviour — it makes poll take a long time. You can see this because it's an operation that takes time without an .await.

The Tokio library provides two things: The loop that polls all the futures you spawn on it, and various replacements for sleeping and IO that cooperate by not spending a lot of time inside poll. For example, you should use delay_for for sleeping inside async functions.

That said, you problem is still not solved, because as you observed, .await will wait for the thing you await. I did have to mention it though, because you were using the thread::sleep function. To spawn a task that runs independently, you can use the tokio::spawn function, which returns a JoinHandle you can await later. This will make the task you spawned start running immediately, and allow you to wait for it later (e.g. you can put them in a vector and loop through them later).

There are also other tools for running things concurrently such as:

  1. the join! macro that waits for a fixed number of tasks concurrently,
  2. the select! macro that waits for a fixed number tasks, but exits when one finishes
  3. the join_all function, which is like join! but for vectors of tasks
  4. the FuturesUnordered collection, which allows you to wait for the next task and add tasks as you go

Note that when you exit main, all other tasks are immediately cancelled.

7 Likes