diff options
author | Stjepan Glavina <stjepang@gmail.com> | 2019-08-16 11:25:25 +0200 |
---|---|---|
committer | Stjepan Glavina <stjepang@gmail.com> | 2019-08-16 11:26:41 +0200 |
commit | 7a8962b617e10ac8d255353d02b6b3640c63ffed (patch) | |
tree | beb9ae5fc2cafbb421dcc98cfde076d3588f6883 /src | |
parent | 1c5032d322ca7ebfda5b47585d584fb13848fec5 (diff) | |
download | async-task-7a8962b617e10ac8d255353d02b6b3640c63ffed.tar.gz |
Rewrite docs
Diffstat (limited to 'src')
-rw-r--r-- | src/join_handle.rs | 92 | ||||
-rw-r--r-- | src/lib.rs | 184 | ||||
-rw-r--r-- | src/raw.rs | 2 | ||||
-rw-r--r-- | src/task.rs | 244 |
4 files changed, 107 insertions, 415 deletions
diff --git a/src/join_handle.rs b/src/join_handle.rs index fb5c275..d4fed50 100644 --- a/src/join_handle.rs +++ b/src/join_handle.rs @@ -12,36 +12,10 @@ use crate::utils::abort_on_panic; /// A handle that awaits the result of a task. /// -/// If the task has completed with `value`, the handle returns it as `Some(value)`. If the task was -/// cancelled or has panicked, the handle returns `None`. Otherwise, the handle has to wait until -/// the task completes, panics, or gets cancelled. +/// This type is a future that resolves to an `Option<R>` where: /// -/// # Examples -/// -/// ``` -/// #![feature(async_await)] -/// -/// use crossbeam::channel; -/// use futures::executor; -/// -/// // The future inside the task. -/// let future = async { 1 + 2 }; -/// -/// // If the task gets woken, it will be sent into this channel. -/// let (s, r) = channel::unbounded(); -/// let schedule = move |task| s.send(task).unwrap(); -/// -/// // Create a task with the future and the schedule function. -/// let (task, handle) = async_task::spawn(future, schedule, ()); -/// -/// // Run the task. In this example, it will complete after a single run. -/// task.run(); -/// assert!(r.is_empty()); -/// -/// // Await the result of the task. -/// let result = executor::block_on(handle); -/// assert_eq!(result, Some(3)); -/// ``` +/// * `None` indicates the task has panicked or was cancelled +/// * `Some(res)` indicates the task has completed with `res` pub struct JoinHandle<R, T> { /// A raw task pointer. pub(crate) raw_task: NonNull<()>, @@ -58,39 +32,9 @@ impl<R, T> Unpin for JoinHandle<R, T> {} impl<R, T> JoinHandle<R, T> { /// Cancels the task. /// - /// When cancelled, the task won't be scheduled again even if a [`Waker`] wakes it. An attempt - /// to run it won't do anything. And if it's completed, awaiting its result evaluates to - /// `None`. - /// - /// [`Waker`]: https://doc.rust-lang.org/std/task/struct.Waker.html - /// - /// # Examples - /// - /// ``` - /// # #![feature(async_await)] - /// use crossbeam::channel; - /// use futures::executor; - /// - /// // The future inside the task. - /// let future = async { 1 + 2 }; + /// If the task has already completed, calling this method will have no effect. /// - /// // If the task gets woken, it will be sent into this channel. - /// let (s, r) = channel::unbounded(); - /// let schedule = move |task| s.send(task).unwrap(); - /// - /// // Create a task with the future and the schedule function. - /// let (task, handle) = async_task::spawn(future, schedule, ()); - /// - /// // Cancel the task. - /// handle.cancel(); - /// - /// // Running a cancelled task does nothing. - /// task.run(); - /// - /// // Await the result of the task. - /// let result = executor::block_on(handle); - /// assert_eq!(result, None); - /// ``` + /// When a task is cancelled, its future cannot be polled again and will be dropped instead. pub fn cancel(&self) { let ptr = self.raw_task.as_ptr(); let header = ptr as *const Header; @@ -139,26 +83,6 @@ impl<R, T> JoinHandle<R, T> { } /// Returns a reference to the tag stored inside the task. - /// - /// # Examples - /// - /// ``` - /// # #![feature(async_await)] - /// use crossbeam::channel; - /// - /// // The future inside the task. - /// let future = async { 1 + 2 }; - /// - /// // If the task gets woken, it will be sent into this channel. - /// let (s, r) = channel::unbounded(); - /// let schedule = move |task| s.send(task).unwrap(); - /// - /// // Create a task with the future and the schedule function. - /// let (task, handle) = async_task::spawn(future, schedule, "a simple task"); - /// - /// // Access the tag. - /// assert_eq!(*handle.tag(), "a simple task"); - /// ``` pub fn tag(&self) -> &T { let offset = Header::offset_tag::<T>(); let ptr = self.raw_task.as_ptr(); @@ -210,9 +134,9 @@ impl<R, T> Drop for JoinHandle<R, T> { Err(s) => state = s, } } else { - // If this is the last reference to task and it's not closed, then close - // it and schedule one more time so that its future gets dropped by the - // executor. + // If this is the last reference to the task and it's not closed, then + // close it and schedule one more time so that its future gets dropped by + // the executor. let new = if state & (!(REFERENCE - 1) | CLOSED) == 0 { SCHEDULED | CLOSED | REFERENCE } else { @@ -1,142 +1,110 @@ //! Task abstraction for building executors. //! -//! # What is an executor? +//! To spawn a future onto an executor, we first need to allocate it on the heap and keep some +//! state alongside it. The state indicates whether the future is ready for polling, waiting to be +//! woken up, or completed. Such a future is called a *task*. //! -//! 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? +//! This crate helps with task allocation and polling its future to completion. //! -//! 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. +//! # Spawning //! -//! 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. +//! All executors have some kind of queue that holds runnable tasks: //! -//! # 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 +//! ``` +//! # #![feature(async_await)] +//! # +//! let (sender, receiver) = crossbeam::channel::unbounded(); +//! # +//! # // A future that will get spawned. +//! # let future = async { 1 + 2 }; +//! # +//! # // A function that schedules the task when it gets woken up. +//! # let schedule = move |task| sender.send(task).unwrap(); +//! # +//! # // Construct a task. +//! # let (task, handle) = async_task::spawn(future, schedule, ()); +//! ``` //! -//! A task is constructed with [`Task::create()`]: +//! A task is constructed using the [`spawn`] function: //! //! ``` //! # #![feature(async_await)] +//! # +//! # let (sender, receiver) = crossbeam::channel::unbounded(); +//! # +//! // A future that will be spawned. //! let future = async { 1 + 2 }; -//! let schedule = |task| unimplemented!(); //! -//! let (task, handle) = async_task::spawn(future, schedule, ()); -//! ``` +//! // A function that schedules the task when it gets woken up. +//! let schedule = move |task| sender.send(task).unwrap(); //! -//! 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. +//! // Construct a task. +//! let (task, handle) = async_task::spawn(future, schedule, ()); //! -//! The second argument is the future that gets polled when the task is run. +//! // Push the task into the queue by invoking its schedule function. +//! task.schedule(); +//! ``` //! -//! 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 last argument to the [`spawn`] function is a *tag*, an arbitrary piece of data associated +//! with the task. In most executors, this is typically a task identifier or task-local storage. //! -//! The constructor returns a runnable [`Task`] and a [`JoinHandle`] that can await the result of -//! the future. +//! The function returns a runnable [`Task`] and a [`JoinHandle`] that can await the result. //! -//! # Task scheduling +//! # Execution //! -//! TODO +//! Task executors have some kind of main loop that drives tasks to completion. That means taking +//! runnable tasks out of the queue and running each one in order: //! -//! # Join handles +//! ```no_run +//! # #![feature(async_await)] +//! # +//! # let (sender, receiver) = crossbeam::channel::unbounded(); +//! # +//! # // A future that will get spawned. +//! # let future = async { 1 + 2 }; +//! # +//! # // A function that schedules the task when it gets woken up. +//! # let schedule = move |task| sender.send(task).unwrap(); +//! # +//! # // Construct a task. +//! # let (task, handle) = async_task::spawn(future, schedule, ()); +//! # +//! # // Push the task into the queue by invoking its schedule function. +//! # task.schedule(); +//! # +//! for task in receiver { +//! task.run(); +//! } +//! ``` //! -//! TODO +//! When a task is run, its future gets polled. If polling does not complete the task, that means +//! it's waiting for another future and needs to go to sleep. When woken up, its schedule function +//! will be invoked, pushing it back into the queue so that it can be run again. //! //! # Cancellation //! -//! TODO +//! Both [`Task`] and [`JoinHandle`] have a method that cancels the task. When cancelled, the +//! task's future will not be polled again and will get dropped instead. +//! +//! If cancelled by the [`Task`] instance, the task is destroyed immediately. If cancelled by the +//! [`JoinHandle`] instance, it will be scheduled one more time and the next attempt to run it will +//! simply destroy it. //! //! # Performance //! -//! TODO: explain single allocation, etc. +//! Task construction incurs a single allocation only that holds its state, the schedule function, +//! and the future or the result of the future if completed. //! -//! 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. +//! The layout of a task is equivalent to 4 words followed by the schedule function, and then by a +//! union of the future and its output. //! -//! [construction]: struct.Task.html#method.create -//! [`JoinHandle`]: struct.JoinHandle.html +//! [`spawn`]: fn.spawn.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); -//! ``` +//! [`JoinHandle`]: struct.JoinHandle.html #![warn(missing_docs, missing_debug_implementations, rust_2018_idioms)] +#![doc(test(attr(deny(rust_2018_idioms, warnings))))] +#![doc(test(attr(allow(unused_extern_crates, unused_variables))))] mod header; mod join_handle; @@ -353,7 +353,7 @@ where let new = (*raw.header).state.fetch_sub(REFERENCE, Ordering::AcqRel) - REFERENCE; // If this was the last reference to the task and the `JoinHandle` has been dropped as - // well, then destroy task. + // well, then destroy the task. if new & !(REFERENCE - 1) == 0 && new & HANDLE == 0 { Self::destroy(ptr); } diff --git a/src/task.rs b/src/task.rs index 8bfc164..b09f602 100644 --- a/src/task.rs +++ b/src/task.rs @@ -10,18 +10,20 @@ use crate::JoinHandle; /// Creates a new task. /// -/// This constructor returns a `Task` reference that runs the future and a [`JoinHandle`] that +/// This constructor returns a [`Task`] reference that runs the future and a [`JoinHandle`] that /// awaits its result. /// -/// The `tag` is stored inside the allocated task. +/// When run, the task polls `future`. When woken, it gets scheduled for running by the `schedule` +/// function. Argument `tag` is an arbitrary piece of data stored inside the task. /// -/// When run, the task polls `future`. When woken, it gets scheduled for running by the -/// `schedule` function. +/// [`Task`]: struct.Task.html +/// [`JoinHandle`]: struct.JoinHandle.html /// /// # Examples /// /// ``` /// # #![feature(async_await)] +/// # /// use crossbeam::channel; /// /// // The future inside the task. @@ -36,8 +38,6 @@ use crate::JoinHandle; /// // Create a task with the future and the schedule function. /// let (task, handle) = async_task::spawn(future, schedule, ()); /// ``` -/// -/// [`JoinHandle`]: struct.JoinHandle.html pub fn spawn<F, R, S, T>(future: F, schedule: S, tag: T) -> (Task<T>, JoinHandle<R, T>) where F: Future<Output = R> + Send + 'static, @@ -57,126 +57,26 @@ where (task, handle) } -/// A task that runs a future. -/// -/// # Construction -/// -/// A task is a heap-allocated structure containing: -/// -/// * A reference counter. -/// * The state of the task. -/// * Arbitrary piece of data called a *tag*. -/// * A function that schedules the task when woken. -/// * A future or its result if polling has completed. -/// -/// Constructor [`Task::create()`] returns a [`Task`] and a [`JoinHandle`]. Those two references -/// are like two sides of the task: one runs the future and the other awaits its result. -/// -/// # Behavior -/// -/// The [`Task`] reference "owns" the task itself and is used to [run] it. Running consumes the -/// [`Task`] reference and polls its internal future. If the future is still pending after being -/// polled, the [`Task`] reference will be recreated when woken by a [`Waker`]. If the future -/// completes, its result becomes available to the [`JoinHandle`]. -/// -/// The [`JoinHandle`] is a [`Future`] that awaits the result of the task. -/// -/// When the task is woken, its [`Task`] reference is recreated and passed to the schedule function -/// provided during construction. In most executors, scheduling simply pushes the [`Task`] into a -/// queue of runnable tasks. -/// -/// If the [`Task`] reference is dropped without being run, the task is cancelled. -/// -/// Both [`Task`] and [`JoinHandle`] have methods that cancel the task. When cancelled, the task -/// won't be scheduled again even if a [`Waker`] wakes it or the [`JoinHandle`] is polled. An -/// attempt to run a cancelled task won't do anything. And if the cancelled task has already -/// completed, awaiting its result through [`JoinHandle`] will return `None`. -/// -/// If polling the task's future panics, it gets cancelled automatically. -/// -/// # Task states -/// -/// A task can be in the following states: -/// -/// * Sleeping: The [`Task`] reference doesn't exist and is waiting to be scheduled by a [`Waker`]. -/// * Scheduled: The [`Task`] reference exists and is waiting to be [run]. -/// * Completed: The [`Task`] reference doesn't exist anymore and can't be rescheduled, but its -/// result is available to the [`JoinHandle`]. -/// * Cancelled: The [`Task`] reference may or may not exist, but running it does nothing and -/// awaiting the [`JoinHandle`] returns `None`. -/// -/// When constructed, the task is initially in the scheduled state. -/// -/// # Destruction +/// A task reference that runs its future. /// -/// The future inside the task gets dropped in the following cases: +/// The [`Task`] reference "owns" the task itself and is able to run it. Running consumes the +/// [`Task`] reference and polls its internal future. If the future is still pending after getting +/// polled, the [`Task`] reference simply won't exist until a [`Waker`] notifies the task. If the +/// future completes, its result becomes available to the [`JoinHandle`]. /// -/// * When [`Task`] is dropped. -/// * When [`Task`] is run to completion. +/// When the task is woken, the [`Task`] reference is recreated and passed to the schedule +/// function. In most executors, scheduling simply pushes the [`Task`] reference into a queue of +/// runnable tasks. /// -/// If the future hasn't been dropped and the last [`Waker`] or [`JoinHandle`] is dropped, or if -/// a [`JoinHandle`] cancels the task, then the task will be scheduled one last time so that its -/// future gets dropped by the executor. In other words, the task's future can be dropped only by -/// [`Task`]. -/// -/// When the task completes, the result of its future is stored inside the allocation. This result -/// is taken out when the [`JoinHandle`] awaits it. When the task is cancelled or the -/// [`JoinHandle`] is dropped without being awaited, the result gets dropped too. -/// -/// The task gets deallocated when all references to it are dropped, which includes the [`Task`], -/// the [`JoinHandle`], and any associated [`Waker`]s. -/// -/// The tag inside the task and the schedule function get dropped at the time of deallocation. -/// -/// # Panics -/// -/// If polling the inner future inside [`run()`] panics, the panic will be propagated into -/// the caller. Likewise, a panic inside the task result's destructor will be propagated. All other -/// panics result in the process being aborted. -/// -/// More precisely, the process is aborted if a panic occurs: -/// -/// * Inside the schedule function. -/// * While dropping the tag. -/// * While dropping the future. -/// * While dropping the schedule function. -/// * While waking the task awaiting the [`JoinHandle`]. +/// If the [`Task`] reference is dropped without being run, the task is cancelled. When cancelled, +/// the task won't be scheduled again even if a [`Waker`] wakes it. It is possible for the +/// [`JoinHandle`] to cancel while the [`Task`] reference exists, in which case an attempt to run +/// the task won't do anything. /// /// [`run()`]: struct.Task.html#method.run -/// [run]: struct.Task.html#method.run /// [`JoinHandle`]: struct.JoinHandle.html /// [`Task`]: struct.Task.html -/// [`Task::create()`]: struct.Task.html#method.create -/// [`Future`]: https://doc.rust-lang.org/std/future/trait.Future.html /// [`Waker`]: https://doc.rust-lang.org/std/task/struct.Waker.html -/// -/// # Examples -/// -/// ``` -/// # #![feature(async_await)] -/// use async_task::Task; -/// use crossbeam::channel; -/// use futures::executor; -/// -/// // The future inside the task. -/// let future = async { -/// println!("Hello, world!"); -/// }; -/// -/// // If the task gets woken, it will be sent into this channel. -/// let (s, r) = channel::unbounded(); -/// let schedule = move |task| s.send(task).unwrap(); -/// -/// // Create a task with the future and the schedule function. -/// let (task, handle) = async_task::spawn(future, schedule, ()); -/// -/// // Run the task. In this example, it will complete after a single run. -/// task.run(); -/// assert!(r.is_empty()); -/// -/// // Await its result. -/// executor::block_on(handle); -/// ``` pub struct Task<T> { /// A pointer to the heap-allocated task. pub(crate) raw_task: NonNull<()>, @@ -195,31 +95,6 @@ impl<T> Task<T> { /// function. /// /// If the task is cancelled, this method won't do anything. - /// - /// # Examples - /// - /// ``` - /// # #![feature(async_await)] - /// use crossbeam::channel; - /// - /// // The future inside the task. - /// let future = async { - /// println!("Hello, world!"); - /// }; - /// - /// // If the task gets woken, it will be sent into this channel. - /// let (s, r) = channel::unbounded(); - /// let schedule = move |task| s.send(task).unwrap(); - /// - /// // Create a task with the future and the schedule function. - /// let (task, handle) = async_task::spawn(future, schedule, ()); - /// - /// // Send the task into the channel. - /// task.schedule(); - /// - /// // Retrieve the task back from the channel. - /// let task = r.recv().unwrap(); - /// ``` pub fn schedule(self) { let ptr = self.raw_task.as_ptr(); let header = ptr as *const Header; @@ -236,9 +111,8 @@ impl<T> Task<T> { /// available to the [`JoinHandle`]. And if the future is still pending, the task will have to /// be woken in order to be rescheduled and then run again. /// - /// If the task is cancelled, running it won't do anything. - /// - /// # Panics + /// If the task was cancelled by a [`JoinHandle`] before it gets run, then this method won't do + /// anything. /// /// It is possible that polling the future panics, in which case the panic will be propagated /// into the caller. It is advised that invocations of this method are wrapped inside @@ -246,33 +120,8 @@ impl<T> Task<T> { /// /// If a panic occurs, the task is automatically cancelled. /// + /// [`JoinHandle`]: struct.JoinHandle.html /// [`catch_unwind`]: https://doc.rust-lang.org/std/panic/fn.catch_unwind.html - /// - /// # Examples - /// - /// ``` - /// # #![feature(async_await)] - /// use crossbeam::channel; - /// use futures::executor; - /// - /// // The future inside the task. - /// let future = async { 1 + 2 }; - /// - /// // If the task gets woken, it will be sent into this channel. - /// let (s, r) = channel::unbounded(); - /// let schedule = move |task| s.send(task).unwrap(); - /// - /// // Create a task with the future and the schedule function. - /// let (task, handle) = async_task::spawn(future, schedule, ()); - /// - /// // Run the task. In this example, it will complete after a single run. - /// task.run(); - /// assert!(r.is_empty()); - /// - /// // Await the result of the task. - /// let result = executor::block_on(handle); - /// assert_eq!(result, Some(3)); - /// ``` pub fn run(self) { let ptr = self.raw_task.as_ptr(); let header = ptr as *const Header; @@ -286,38 +135,9 @@ impl<T> Task<T> { /// Cancels the task. /// /// When cancelled, the task won't be scheduled again even if a [`Waker`] wakes it. An attempt - /// to run it won't do anything. And if it's completed, awaiting its result evaluates to - /// `None`. + /// to run it won't do anything. /// /// [`Waker`]: https://doc.rust-lang.org/std/task/struct.Waker.html - /// - /// # Examples - /// - /// ``` - /// # #![feature(async_await)] - /// use crossbeam::channel; - /// use futures::executor; - /// - /// // The future inside the task. - /// let future = async { 1 + 2 }; - /// - /// // If the task gets woken, it will be sent into this channel. - /// let (s, r) = channel::unbounded(); - /// let schedule = move |task| s.send(task).unwrap(); - /// - /// // Create a task with the future and the schedule function. - /// let (task, handle) = async_task::spawn(future, schedule, ()); - /// - /// // Cancel the task. - /// task.cancel(); - /// - /// // Running a cancelled task does nothing. - /// task.run(); - /// - /// // Await the result of the task. - /// let result = executor::block_on(handle); - /// assert_eq!(result, None); - /// ``` pub fn cancel(&self) { let ptr = self.raw_task.as_ptr(); let header = ptr as *const Header; @@ -328,26 +148,6 @@ impl<T> Task<T> { } /// Returns a reference to the tag stored inside the task. - /// - /// # Examples - /// - /// ``` - /// # #![feature(async_await)] - /// use crossbeam::channel; - /// - /// // The future inside the task. - /// let future = async { 1 + 2 }; - /// - /// // If the task gets woken, it will be sent into this channel. - /// let (s, r) = channel::unbounded(); - /// let schedule = move |task| s.send(task).unwrap(); - /// - /// // Create a task with the future and the schedule function. - /// let (task, handle) = async_task::spawn(future, schedule, "a simple task"); - /// - /// // Access the tag. - /// assert_eq!(*task.tag(), "a simple task"); - /// ``` pub fn tag(&self) -> &T { let offset = Header::offset_tag::<T>(); let ptr = self.raw_task.as_ptr(); |