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}