You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
150 lines
5.1 KiB
Rust
150 lines
5.1 KiB
Rust
//! Task abstraction for building executors.
|
|
//!
|
|
//! # What is an executor?
|
|
//!
|
|
//! An async block creates a future and an async function returns one. But futures don't do
|
|
//! anything unless they are awaited inside other async blocks or async functions. So the question
|
|
//! arises: who or what awaits the main future that awaits others?
|
|
//!
|
|
//! One solution is to call [`block_on()`] on the main future, which will block
|
|
//! the current thread and keep polling the future until it completes. But sometimes we don't want
|
|
//! to block the current thread and would prefer to *spawn* the future to let a background thread
|
|
//! block on it instead.
|
|
//!
|
|
//! This is where executors step in - they create a number of threads (typically equal to the
|
|
//! number of CPU cores on the system) that are dedicated to polling spawned futures. Each executor
|
|
//! thread keeps polling spawned futures in a loop and only blocks when all spawned futures are
|
|
//! either sleeping or running.
|
|
//!
|
|
//! # What is a task?
|
|
//!
|
|
//! In order to spawn a future on an executor, one needs to allocate the future on the heap and
|
|
//! keep some state alongside it, like whether the future is ready for polling, waiting to be woken
|
|
//! up, or completed. This allocation is usually called a *task*.
|
|
//!
|
|
//! The executor then runs the spawned task by polling its future. If the future is pending on a
|
|
//! resource, a [`Waker`] associated with the task will be registered somewhere so that the task
|
|
//! can be woken up and run again at a later time.
|
|
//!
|
|
//! For example, if the future wants to read something from a TCP socket that is not ready yet, the
|
|
//! networking system will clone the task's waker and wake it up once the socket becomes ready.
|
|
//!
|
|
//! # Task construction
|
|
//!
|
|
//! A task is constructed with [`Task::create()`]:
|
|
//!
|
|
//! ```
|
|
//! # #![feature(async_await)]
|
|
//! let future = async { 1 + 2 };
|
|
//! let schedule = |task| unimplemented!();
|
|
//!
|
|
//! let (task, handle) = async_task::spawn(future, schedule, ());
|
|
//! ```
|
|
//!
|
|
//! The first argument to the constructor, `()` in this example, is an arbitrary piece of data
|
|
//! called a *tag*. This can be a task identifier, a task name, task-local storage, or something
|
|
//! of similar nature.
|
|
//!
|
|
//! The second argument is the future that gets polled when the task is run.
|
|
//!
|
|
//! The third argument is the schedule function, which is called every time when the task gets
|
|
//! woken up. This function should push the received task into some kind of queue of runnable
|
|
//! tasks.
|
|
//!
|
|
//! The constructor returns a runnable [`Task`] and a [`JoinHandle`] that can await the result of
|
|
//! the future.
|
|
//!
|
|
//! # Task scheduling
|
|
//!
|
|
//! TODO
|
|
//!
|
|
//! # Join handles
|
|
//!
|
|
//! TODO
|
|
//!
|
|
//! # Cancellation
|
|
//!
|
|
//! TODO
|
|
//!
|
|
//! # Performance
|
|
//!
|
|
//! TODO: explain single allocation, etc.
|
|
//!
|
|
//! Task [construction] incurs a single allocation only. The [`Task`] can then be run and its
|
|
//! result awaited through the [`JoinHandle`]. When woken, the task gets automatically rescheduled.
|
|
//! It's also possible to cancel the task so that it stops running and can't be awaited anymore.
|
|
//!
|
|
//! [construction]: struct.Task.html#method.create
|
|
//! [`JoinHandle`]: struct.JoinHandle.html
|
|
//! [`Task`]: struct.Task.html
|
|
//! [`Future`]: https://doc.rust-lang.org/nightly/std/future/trait.Future.html
|
|
//! [`Waker`]: https://doc.rust-lang.org/nightly/std/task/struct.Waker.html
|
|
//! [`block_on()`]: https://docs.rs/futures-preview/*/futures/executor/fn.block_on.html
|
|
//!
|
|
//! # Examples
|
|
//!
|
|
//! A simple single-threaded executor:
|
|
//!
|
|
//! ```
|
|
//! # #![feature(async_await)]
|
|
//! use std::future::Future;
|
|
//! use std::panic::catch_unwind;
|
|
//! use std::thread;
|
|
//!
|
|
//! use async_task::{JoinHandle, Task};
|
|
//! use crossbeam::channel::{unbounded, Sender};
|
|
//! use futures::executor;
|
|
//! use lazy_static::lazy_static;
|
|
//!
|
|
//! /// Spawns a future on the executor.
|
|
//! fn spawn<F, R>(future: F) -> JoinHandle<R, ()>
|
|
//! where
|
|
//! F: Future<Output = R> + Send + 'static,
|
|
//! R: Send + 'static,
|
|
//! {
|
|
//! lazy_static! {
|
|
//! // A channel that holds scheduled tasks.
|
|
//! static ref QUEUE: Sender<Task<()>> = {
|
|
//! let (sender, receiver) = unbounded::<Task<()>>();
|
|
//!
|
|
//! // Start the executor thread.
|
|
//! thread::spawn(|| {
|
|
//! for task in receiver {
|
|
//! // Ignore panics for simplicity.
|
|
//! let _ignore_panic = catch_unwind(|| task.run());
|
|
//! }
|
|
//! });
|
|
//!
|
|
//! sender
|
|
//! };
|
|
//! }
|
|
//!
|
|
//! // Create a task that is scheduled by sending itself into the channel.
|
|
//! let schedule = |t| QUEUE.send(t).unwrap();
|
|
//! let (task, handle) = async_task::spawn(future, schedule, ());
|
|
//!
|
|
//! // Schedule the task by sending it into the channel.
|
|
//! task.schedule();
|
|
//!
|
|
//! handle
|
|
//! }
|
|
//!
|
|
//! // Spawn a future and await its result.
|
|
//! let handle = spawn(async {
|
|
//! println!("Hello, world!");
|
|
//! });
|
|
//! executor::block_on(handle);
|
|
//! ```
|
|
|
|
#![warn(missing_docs, missing_debug_implementations, rust_2018_idioms)]
|
|
|
|
mod header;
|
|
mod join_handle;
|
|
mod raw;
|
|
mod state;
|
|
mod task;
|
|
mod utils;
|
|
|
|
pub use crate::join_handle::JoinHandle;
|
|
pub use crate::task::{spawn, Task};
|