diff options
author | Eliza Weisman <eliza@buoyant.io> | 2019-11-20 14:36:45 -0800 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-11-20 14:36:45 -0800 |
commit | c223db35896bd6b6714d4793cd84290e573bb216 (patch) | |
tree | 6c751e3aed6f915a7a48e88365e2fe4420566135 /tokio | |
parent | 5cd665afd7b70b184b559e6407fdf645983e1314 (diff) |
docs: improve `tokio::task` API documentation (#1801)
## Motivation
The new `tokio::task` module is pretty lacking in API docs.
## Solution
This branch adds new API docs to the `task` module, including:
* Module-level docs with a summary of the differences between
tasks and threads
* Examples of how to use the `task` APIs in the module-level docs
* More docs for `yield_now`
* More docs and examples for `JoinHandle`, based on the
`std::thread::JoinHandle` API docs.
This branch contains commits cherry-picked from #1794
Signed-off-by: Eliza Weisman <eliza@buoyant.io>
Diffstat (limited to 'tokio')
-rw-r--r-- | tokio/src/task/join.rs | 67 | ||||
-rw-r--r-- | tokio/src/task/mod.rs | 209 |
2 files changed, 275 insertions, 1 deletions
diff --git a/tokio/src/task/join.rs b/tokio/src/task/join.rs index c2c59a2c..5ae93ea8 100644 --- a/tokio/src/task/join.rs +++ b/tokio/src/task/join.rs @@ -8,6 +8,73 @@ use std::pin::Pin; use std::task::{Context, Poll}; /// An owned permission to join on a task (await its termination). +/// +/// This can be thought of as the equivalent of [`std::thread::JoinHandle`] for +/// a task rather than a thread. +/// +/// A `JoinHandle` *detaches* the associated task when it is dropped, which +/// means that there is no longer any handle to the task, and no way to `join` +/// on it. +/// +/// This `struct` is created by the [`task::spawn`] and [`task::spawn_blocking`] +/// functions. +/// +/// # Examples +/// +/// Creation from [`task::spawn`]: +/// +/// ``` +/// use tokio::task; +/// +/// # async fn doc() { +/// let join_handle: task::JoinHandle<_> = task::spawn(async { +/// // some work here +/// }); +/// # } +/// ``` +/// +/// Creation from [`task::spawn_blocking`]: +/// +/// ``` +/// use tokio::task; +/// +/// # async fn doc() { +/// let join_handle: task::JoinHandle<_> = task::spawn_blocking(|| { +/// // some blocking work here +/// }); +/// # } +/// ``` +/// +/// Child being detached and outliving its parent: +/// +/// ```no_run +/// use tokio::task; +/// use tokio::time; +/// use std::time::Duration; +/// +/// # #[tokio::main] async fn main() { +/// let original_task = task::spawn(async { +/// let _detached_task = task::spawn(async { +/// // Here we sleep to make sure that the first task returns before. +/// time::delay_for(Duration::from_millis(10)).await; +/// // This will be called, even though the JoinHandle is dropped. +/// println!("♫ Still alive ♫"); +/// }); +/// }); +/// +/// original_task.await.expect("The task being joined has panicked"); +/// println!("Original task is joined."); +/// +/// // We make sure that the new task has time to run, before the main +/// // task returns. +/// +/// time::delay_for(Duration::from_millis(1000)).await; +/// # } +/// ``` +/// +/// [`task::spawn`]: crate::task::spawn() +/// [`task::spawn_blocking`]: crate::task::spawn_blocking +/// [`std::thread::JoinHandle`]: std::thread::JoinHandle pub struct JoinHandle<T> { raw: Option<RawTask>, _p: PhantomData<T>, diff --git a/tokio/src/task/mod.rs b/tokio/src/task/mod.rs index 7b27ff1b..39d25ec8 100644 --- a/tokio/src/task/mod.rs +++ b/tokio/src/task/mod.rs @@ -1,5 +1,212 @@ //! 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][rt]. Another name for this general patterh 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: +//! +//! * Tasks are **light weight**. Because tasks are scheduled by the Tokio +//! runtime rather than the operating system, creating new tasks or switching +//! between tasks does not require a context switch and has fairly low +//! overhead. Creating, running, and destroying large numbers of tasks is +//! quite cheap, especially compared to OS threads. +//! +//! * Tasks are scheduled **cooperatively**. Most operating systems implement +//! _preemptive multitasking_. This is a scheduling technique where the +//! operating system allows each thread to run for a period of time, and then +//! _preempts_ it, temporarily pausing that thread and switching to another. +//! Tasks, on the other hand, implement _cooperative multitasking_. In +//! cooperative multitasking, a task is allowed to run until it _yields_, +//! indicating to the Tokio runtime's scheduler that it cannot currently +//! continue executing. When a task yields, the Tokio runtime switches to +//! executing the next task. +//! +//! * Tasks are **non-blocking**. Typically, when an OS thread performs I/O or +//! must synchronize with another thread, it _blocks_, allowing the OS to +//! schedule another thread. When a task cannot continue executing, it must +//! yield instead, allowing the Tokio runtime to schedule another task. Tasks +//! should generally not perform system calls or other operations that could +//! block a thread, as this would prevent other tasks running on the same +//! thread from executing as well. Instead, this module provides APIs for +//! running blocking operations in an asynchronous context. +//! +//! [rt]: crate::runtime +//! [green threads]: https://en.wikipedia.org/wiki/Green_threads +//! [Go's goroutines]: https://tour.golang.org/concurrency/1 +//! [Kotlin's coroutines]: https://kotlinlang.org/docs/reference/coroutines-overview.html +//! [Erlang's processes]: http://erlang.org/doc/getting_started/conc_prog.html#processes +//! +//! ## 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`][thread_spawn]. It takes an `async` block or other [future], +//! and creates a new task to run that work concurrently: +//! +//! ``` +//! use tokio::task; +//! +//! # async fn doc() { +//! 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; +//! +//! # #[tokio::main] async fn main() -> Result<(), Box<dyn std::error::Error>> { +//! let join = task::spawn(async { +//! // ... +//! "hello world!" +//! }); +//! +//! // ... +//! +//! // Await the result of the spawned task. +//! let result = join.await?; +//! assert_eq!(result, "hello world!"); +//! # Ok(()) +//! # } +//! ``` +//! +//! Again, like `std::thread`'s [`JoinHandle` type][thread_join], if the spawned +//! task panics, awaiting its `JoinHandle` will return a [`JoinError`]`. For +//! example: +//! +//! ``` +//! use tokio::task; +//! +//! # #[tokio::main] async fn main() { +//! 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-core" +//! feature flag is enabled. +//! +//! [`task::spawn`]: crate::task::spawn() +//! [future]: std::future::Future +//! [`std::thread::spawn`]: std::thread::spawn +//! [`JoinHandle`]: crate::task::JoinHandle +//! [thread_join]: std::thread::JoinHandle +//! [`JoinError`]: crate::task::JoinError +//! +//! ### 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`]. +//! +//! 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; +//! +//! # async fn docs() { +//! 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: +//! +//! ```rust +//! # use tokio::task; +//! # async fn docs() -> Result<(), Box<dyn std::error::Error>>{ +//! 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"); +//! # Ok(()) +//! # } +//! ``` +//! +//! When using the [threaded runtime][rt-threaded], 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; +//! +//! # async fn docs() { +//! let result = task::block_in_place(|| { +//! // do some compute-heavy work or call synchronous code +//! "blocking completed" +//! }); +//! +//! assert_eq!(result, "blocking completed"); +//! # } +//! ``` +//! +//! In addition, this module also provides a [`task::yield_now`] async function +//! that is analogous to the standard library's [`thread::yield_now`]. Calling and +//! `await`ing this function will cause the current task to yield to the Tokio +//! runtime's scheduler, allowing another task to be scheduled. Eventually, the +//! yielding task will be polled again, allowing it to execute. For example: +//! +//! ```rust +//! use tokio::task; +//! +//! # #[tokio::main] async fn main() { +//! 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!"); +//! } +//! # .await; +//! # } +//! ``` +//! +//! [`task::spawn_blocking`]: crate::task::spawn_blocking +//! [`task::block_in_place`]: crate::task::block_in_place +//! [rt-threaded]: crate::runtime::Runtime::builder::threaded_scheduler +//! [`task::yield_now`]: crate::task::yield_now() +//! [`thread::yield_now`]: std::thread::yield_now cfg_blocking! { mod blocking; pub use blocking::spawn_blocking; |