vexide_async/
task.rs

1//! Asynchronous multitasking.
2//!
3//! This module provides utilities for working with asynchronous tasks.
4//!
5//! A *task* is a light weight, non-blocking unit of execution. Tasks allow you to cooperatively
6//! perform work in the background without blocking other code from running.
7//!
8//! - Tasks are **light weight**. Because tasks are scheduled and managed by vexide, creating new
9//!   tasks or switching between tasks does not require a context switch and has fairly low
10//!   overhead. Creating, running, and destroying large numbers of tasks is relatively cheap in
11//!   comparison to traditional threads.
12//!
13//! - Tasks are scheduled **cooperatively**. Most operating system threads implement *preemptive
14//!   multitasking*. This is a scheduling technique where the operating system allows each thread to
15//!   run for a period of time, and then forcibly preempts it, temporarily pausing that thread and
16//!   switching to another. Tasks, on the other hand, implement *cooperative multitasking*. In
17//!   cooperative multitasking, a task will run until it voluntarily yields using an `await` point,
18//!   giving control back to the vexide runtime's scheduler. When a task yields by `await`ing
19//!   something, the vexide runtime switches to executing a different task.
20//!
21//! - Tasks are **non-blocking**. Typically, when an OS thread performs I/O or must synchronize with
22//!   another thread, it *blocks*, allowing the OS to schedule another thread. When a task cannot
23//!   continue executing, it should yield instead, allowing the vexide runtime to schedule another
24//!   task in its place. Tasks should generally not perform operations that could block the CPU for
25//!   a long period of time without an `await` point, as this would prevent other tasks from
26//!   executing as well. This includes situations involving long-running "tight loops" (`loop {}`)
27//!   without `await` points.
28//!
29//! # Spawning Tasks
30//!
31//! Perhaps the most important function in this module is [`spawn`]. This function can be thought of
32//! as an async equivalent to the standard library’s [`thread::spawn`](std::thread::spawn). It takes
33//! an `async` block or other [future](std::future), and creates a new task that runs it
34//! concurrently in the background:
35//!
36//! ```
37//! # #[vexide::main]
38//! # async fn main(_peripherals: vexide::peripherals::Peripherals) {
39//! use vexide::task;
40//!
41//! task::spawn(async {
42//!     // perform some work here...
43//! });
44//! # }
45//! ```
46//!
47//! After a task is spawned, you are given a [`Task`] struct, representing a running (or previously
48//! running) task. The [`Task`] struct is itself a future which may be used to await the output of
49//! the spawned task. For example:
50//!
51//! ```
52//! # #[vexide::main]
53//! # async fn main(_peripherals: vexide::peripherals::Peripherals) {
54//! use vexide::task;
55//!
56//! let task = task::spawn(async {
57//!     // ...
58//!     "hello world!"
59//! });
60//!
61//! // ...
62//!
63//! // Await the result of the spawned task.
64//! let result = task.await;
65//! assert_eq!(result, "hello world!");
66//! # }
67//! ```
68//!
69//! # Cancellation
70//!
71//! When a [`Task`] is dropped, it will stop being polled by vexide's runtime. This means that a
72//! task is cancelled when it leaves the scope it was spawned in.
73//!
74//! ```no_run
75//! # #[vexide::main]
76//! # async fn main(_peripherals: vexide::peripherals::Peripherals) {
77//! use std::time::Duration;
78//!
79//! use vexide::{task, time::sleep};
80//!
81//! {
82//!     // This task will never run, since it immediately falls out of scope after it's spawned.
83//!     let task = task::spawn(async {
84//!         loop {
85//!             println!("Hiiiii :3");
86//!             sleep(Duration::from_secs(1)).await;
87//!         }
88//!     });
89//! }
90//! # }
91//! ```
92//!
93//! If a task must outlive the scope it was spawned in, you can [`detach`] it. This lets the task
94//! run in the background beyond its current scope. When we `detach` a task, we lose its [`Task`]
95//! handle and therefore have no way to `await` its output. As a result, detached tasks may run
96//! forever with no way of being stopped.
97//!
98//! [`detach`]: Task::detach
99//!
100//! ```no_run
101//! # #[vexide::main]
102//! # async fn main(_peripherals: vexide::peripherals::Peripherals) {
103//! use std::time::Duration;
104//!
105//! use vexide::{task, time::sleep};
106//!
107//! {
108//!     let task = task::spawn(async {
109//!         loop {
110//!             println!("Hiiiii :3");
111//!             sleep(Duration::from_secs(1)).await;
112//!         }
113//!     });
114//!
115//!     // Run it forever, even after it leaves scope.
116//!     task.detach();
117//! }
118//! # }
119//! ```
120//!
121//! # Sharing State Between Tasks
122//!
123//! When running multiple tasks at once, it's often useful to share some data between them.
124//!
125//! To do this, we need multiple owners of the same piece of data, which is something that Rust's
126//! borrow checker forbids. An easy way around this is to combine an [`Rc`] with a [`RefCell`],
127//! which gives us both interior mutability and multiple owners. By wrapping our shared state in
128//! `Rc<RefCell<T>>`, we can clone a smart pointer to it across as many tasks as we want.
129//!
130//! [`Rc`]: std::rc::Rc
131//! [`RefCell`]: std::cell::RefCell
132//!
133//! ```no_run
134//! # #[vexide::main]
135//! # async fn main(_peripherals: vexide::peripherals::Peripherals) {
136//! use std::{cell::RefCell, rc::Rc, time::Duration};
137//!
138//! use vexide::{task, time::sleep};
139//!
140//! let counter = Rc::new(RefCell::new(1));
141//!
142//! // task_1 increments `counter` every second.
143//! let task_1 = task::spawn({
144//!     let counter = counter.clone();
145//!
146//!     async move {
147//!         loop {
148//!             *counter.borrow_mut() += 1;
149//!             sleep(Duration::from_secs(1)).await;
150//!         }
151//!     }
152//! });
153//!
154//! // task_2 prints `counter` every two seconds.
155//! let task_2 = task::spawn(async move {
156//!     loop {
157//!         println!("Counter: {}", *counter.borrow());
158//!
159//!         sleep(Duration::from_secs(2)).await;
160//!     }
161//! });
162//! # }
163//! ```
164//!
165//! More complex use-cases may require you to hold ownership of shared state *across*
166//! `await`-points. In these cases, a simple `Rc<RefCell<T>>` will not suffice, since another
167//! running task may claim ownership of the data, which would cause the program to panic. Doing this
168//! effectively requires the use of a *synchronization primitive* like a
169//! [`Mutex`](crate::sync::Mutex) or [`RwLock`](crate::sync::RwLock) to manage safe access to shared
170//! state across multiple running tasks.
171//!
172//! For more information on how to do this, see vexide's [`sync`](crate::sync) module.
173
174use std::{future::Future, rc::Rc};
175
176pub use crate::local::{LocalKey, task_local};
177use crate::{executor::EXECUTOR, local::TaskLocalStorage};
178
179// public because it's used in Task<T> and InfallibleTask<T>
180#[doc(hidden)]
181#[derive(Debug)]
182pub struct TaskMetadata {
183    pub(crate) tls: Rc<TaskLocalStorage>,
184}
185
186/// A spawned task.
187///
188/// A [`Task`] can be awaited to retrieve the output of its future.
189///
190/// Dropping a [`Task`] cancels it, which means its future won't be polled again. To drop the
191/// [`Task`] handle without canceling it, use [`detach()`][`Task::detach()`] instead. To cancel a
192/// task gracefully and wait until it is fully destroyed, use the [`cancel()`][Task::cancel()]
193/// method.
194///
195/// # Examples
196///
197/// ```
198/// use vexide::prelude::*;
199///
200/// #[vexide::main]
201/// async fn main(_peripherals: Peripherals) {
202///     // Spawn a future onto the executor.
203///     let task = vexide::task::spawn(async {
204///         println!("Hello from a task!");
205///         1 + 2
206///     });
207///
208///     // Wait for the task's output.
209///     assert_eq!(task.await, 3);
210/// }
211/// ```
212pub type Task<T> = async_task::Task<T, TaskMetadata>;
213
214/// A spawned task with a fallible response.
215pub type FallibleTask<T> = async_task::FallibleTask<T, TaskMetadata>;
216
217/// Spawns a new async task that can be controlled with the returned task handle.
218pub fn spawn<T>(future: impl Future<Output = T> + 'static) -> Task<T> {
219    EXECUTOR.with(|ex| ex.spawn(future))
220}