mirror of
https://github.com/tokio-rs/tokio.git
synced 2026-09-09 00:00:08 +02:00
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 <[email protected]>
This commit is contained in:
@@ -8,6 +8,73 @@ use std::pin::Pin;
|
|||||||
use std::task::{Context, Poll};
|
use std::task::{Context, Poll};
|
||||||
|
|
||||||
/// An owned permission to join on a task (await its termination).
|
/// 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> {
|
pub struct JoinHandle<T> {
|
||||||
raw: Option<RawTask>,
|
raw: Option<RawTask>,
|
||||||
_p: PhantomData<T>,
|
_p: PhantomData<T>,
|
||||||
|
|||||||
+208
-1
@@ -1,5 +1,212 @@
|
|||||||
//! Asynchronous green-threads.
|
//! 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! {
|
cfg_blocking! {
|
||||||
mod blocking;
|
mod blocking;
|
||||||
pub use blocking::spawn_blocking;
|
pub use blocking::spawn_blocking;
|
||||||
|
|||||||
Reference in New Issue
Block a user