Runtime

In the previous section we explored Futures and Streams which allow us to representa value (in the case of a Future) or a series of values (in the case of Stream)that will be available “at some point in the future”. We talked about poll onFuture and Stream which the runtime will call to figure out if the Future orthe Stream are ready to yield a value.

Lastly, we said that the runtime is needed to poll Futures and Streams driving themto completion. We’ll take a closer look at the runtime now.

Tokio runtime

In order for a Future to make progress, something has to call poll. This is thejob of the runtime.

The runtime is responsible for repeatedly calling poll on a Future until itsvalue is returned. There are many different ways to do this and thus many types ofruntime configurations. For example, the CurrentThread runtime configurationwill block the current thread and loop through all spawned Futures, calling poll onthem. The ThreadPool configuration schedules Futures across a thread pool. Thisis also the default configuration used by the Tokio runtime.

It’s important to remember that all futures must be spawned on the runtime or nowork will be performed.

Spawning Tasks

One of the unique aspects of Tokio is that futures can be spawned on the runtime fromwithin other futures or streams. When we use futures in this way, we usually refer tothem as tasks. Tasks are the application’s “unit of logic”. They are similar to Go’sgoroutine and Erlang’s process, but asynchronous. In other words, tasks areasynchronous green threads.

Given that a task runs an asynchronous bit of logic, they are represented by theFuture trait. The task’s future implementation completes with a () value once thetask is done processing.

Tasks are passed to the runtime, which handle scheduling the task. The runtime isusually scheduling many tasks across a single or small set of threads. Tasks must notperform computation-heavy logic or they will prevent other tasks from executing. Sodon’t try to compute the fibonacci sequence as a task!

Tasks are implemented by either building up a future using the various combinatorfunctions available in the futures and tokio crates or by implementing the Futuretrait directly.

We can spawn tasks using tokio::spawn. For example:

  1. # extern crate tokio;
  2. # extern crate futures;
  3. #
  4. # use tokio::prelude::*;
  5. # use futures::stream;
  6. # fn main() {
  7. # let my_outer_stream = stream::once(Ok(1));
  8. // Create some kind of future that we want our runtime to execute
  9. let program = my_outer_stream.for_each(|my_outer_value| {
  10. println!("Got value {:?} from the stream", my_outer_value);
  11. let my_inner_future = future::ok(1);
  12. let task = my_inner_future.and_then(|my_inner_value| {
  13. println!("Got a value {:?} from second future", my_inner_value);
  14. Ok(())
  15. });
  16. tokio::spawn(task);
  17. Ok(())
  18. });
  19. tokio::run(program);
  20. # }

Again spawning tasks can happen within other futures or streams allowing multiplethings to happen concurrently. In the above example we’re spawning the inner futurefrom within the outer stream. Each time we get a value from the stream we’ll simplyrun inner future.

In the next section, we’ll take a look at a more involved example than our hello-world example that takes everything we’ve learned so far into account.

Next up: Example: An Echo Server