[][src]Module tokio::task

Asynchronous green-threads.

What are Tasks?

A task is a light weight, non-blocking unit of execution. A task is similar to an OS thread, but rather than being managed by the OS scheduler, they are managed by the Tokio runtime. Another name for this general pattern is green threads. If you are familiar with Go's goroutines, Kotlin's coroutines, or Erlang's processes, you can think of Tokio's tasks as something similar.

Key points about tasks include:

Working with Tasks

This module provides the following APIs for working with tasks:

Spawning

Perhaps the most important function in this module is task::spawn. This function can be thought of as an async equivalent to the standard library's thread::spawn. It takes an async block or other future, and creates a new task to run that work concurrently:

use tokio::task;

task::spawn(async {
    // perform some work here...
});

Like std::thread::spawn, task::spawn returns a JoinHandle struct. A JoinHandle is itself a future which may be used to await the output of the spawned task. For example:

use tokio::task;

let join = task::spawn(async {
    // ...
    "hello world!"
});

// ...

// Await the result of the spawned task.
let result = join.await?;
assert_eq!(result, "hello world!");

Again, like std::thread's JoinHandle type, if the spawned task panics, awaiting its JoinHandle will return a JoinError`. For example:

use tokio::task;

let join = task::spawn(async {
    panic!("something bad happened!")
});

// The returned result indicates that the task failed.
assert!(join.await.is_err());

spawn, JoinHandle, and JoinError are present when the "rt" feature flag is enabled.

Blocking and Yielding

As we discussed above, code running in asynchronous tasks should not perform operations that can block. A blocking operation performed in a task running on a thread that is also running other tasks would block the entire thread, preventing other tasks from running.

Instead, Tokio provides two APIs for running blocking operations in an asynchronous context: task::spawn_blocking and task::block_in_place.

spawn_blocking

The task::spawn_blocking function is similar to the task::spawn function discussed in the previous section, but rather than spawning an non-blocking future on the Tokio runtime, it instead spawns a blocking function on a dedicated thread pool for blocking tasks. For example:

use tokio::task;

task::spawn_blocking(|| {
    // do some compute-heavy work or call synchronous code
});

Just like task::spawn, task::spawn_blocking returns a JoinHandle which we can use to await the result of the blocking operation:

let join = task::spawn_blocking(|| {
    // do some compute-heavy work or call synchronous code
    "blocking completed"
});

let result = join.await?;
assert_eq!(result, "blocking completed");

block_in_place

When using the multi-threaded runtime, the task::block_in_place function is also available. Like task::spawn_blocking, this function allows running a blocking operation from an asynchronous context. Unlike spawn_blocking, however, block_in_place works by transitioning the current worker thread to a blocking thread, moving other tasks running on that thread to another worker thread. This can improve performance by avoiding context switches.

For example:

use tokio::task;

let result = task::block_in_place(|| {
    // do some compute-heavy work or call synchronous code
    "blocking completed"
});

assert_eq!(result, "blocking completed");

yield_now

In addition, this module provides a task::yield_now async function that is analogous to the standard library's thread::yield_now. Calling and awaiting this function will cause the current task to yield to the Tokio runtime's scheduler, allowing other tasks to be scheduled. Eventually, the yielding task will be polled again, allowing it to execute. For example:

use tokio::task;

async {
    task::spawn(async {
        // ...
        println!("spawned task done!")
    });

    // Yield, allowing the newly-spawned task to execute first.
    task::yield_now().await;
    println!("main task done!");
}

Cooperative scheduling

A single call to poll on a top-level task may potentially do a lot of work before it returns Poll::Pending. If a task runs for a long period of time without yielding back to the executor, it can starve other tasks waiting on that executor to execute them, or drive underlying resources. Since Rust does not have a runtime, it is difficult to forcibly preempt a long-running task. Instead, this module provides an opt-in mechanism for futures to collaborate with the executor to avoid starvation.

Consider a future like this one:

async fn drop_all<I: Stream + Unpin>(mut input: I) {
    while let Some(_) = input.next().await {}
}

It may look harmless, but consider what happens under heavy load if the input stream is always ready. If we spawn drop_all, the task will never yield, and will starve other tasks and resources on the same executor.

To account for this, Tokio has explicit yield points in a number of library functions, which force tasks to return to the executor periodically.

unconstrained

If necessary, task::unconstrained lets you opt out a future of Tokio's cooperative scheduling. When a future is wrapped with unconstrained, it will never be forced to yield to Tokio. For example:

use tokio::{task, sync::mpsc};

let fut = async {
    let (tx, mut rx) = mpsc::unbounded_channel();

    for i in 0..1000 {
        let _ = tx.send(());
        // This will always be ready. If coop was in effect, this code would be forced to yield
        // periodically. However, if left unconstrained, then this code will never yield.
        rx.recv().await;
    }
};

task::unconstrained(fut).await;

Structs

JoinError

Task failed to execute to completion.

JoinHandle

An owned permission to join on a task (await its termination).

LocalKey

A key for task-local data.

LocalSet

A set of tasks which are executed on the same thread.

Unconstrained

Future for the unconstrained method.

Functions

block_in_place

Runs the provided blocking function on the current thread without blocking the executor.

spawn

Spawns a new asynchronous task, returning a JoinHandle for it.

spawn_blocking

Runs the provided closure on a thread where blocking is acceptable.

spawn_local

Spawns a !Send future on the local task set.

unconstrained

Turn off cooperative scheduling for a future. The future will never be forced to yield by Tokio. Using this exposes your service to starvation if the unconstrained future never yields otherwise.

yield_now

Yields execution back to the Tokio runtime.