1 unstable release

0.1.0 Mar 17, 2024

#16 in #spawn

Custom license

8KB
102 lines

Parasitic Task

Parasitic tasks are tasks that are not scheduled by any executor, but are instead directly dispatched at once on the thread that invokes the waker.

This makes it possible to process asynchronous results just-in-place, without the need for queueing the subsequent operations on a separate thread. This is useful for situations where response latency is a critical issue, such as in high-frequency trading applications.

Usage

Simply run a parasitic task with ptask::spawn which is immediately dispatched on the current thread, until it awaits for some future that has not yet completed, typically receiving from an asynchronous channel.

Note that the task is ref-counted with Arc internally, as the asynchonous operations that are awaited must be holding the waker that keeps the task alive. Thus, the sender could be regarded as the owner of the task that is awaiting for the receiver. Keep in mind that if there are multiple ptasks that holds the senders of each other, some senders has to be wrapped within Weak references to avoid circular references.

use futures::{channel::mpsc, StreamExt};
use std::sync::{
    atomic::{AtomicUsize, Ordering},
    Arc,
};

// This demo shows how it works on a single thread.
#[test]
fn demo() {
    let (tx, mut rx) = mpsc::unbounded();
    let result = Arc::new(AtomicUsize::new(0));

    // Spawn a ptask that runs immediately until the first `await`.
    // Then the `await`ed channel holds the reference to the task.
    ptask::spawn({
        let result = result.clone();
        async move {
            while let Some(i) = rx.next().await {
                result.fetch_add(i, Ordering::Relaxed);
            }
            result.fetch_add(10, Ordering::Relaxed);
        }
    });

    for i in 0..10 {
        // Sending to the channel makes the ptask resumes running until the next `await`.
        tx.unbounded_send(i).unwrap();
        assert_ne!(result.load(Ordering::Relaxed), 55);
    }

    // Drop the sender so that the ptask breaks out of the loop.
    drop(tx);

    assert_eq!(result.load(Ordering::Relaxed), 55);
}

Capabilities

This crate supports no_std but requires the alloc crate to be available.

Optional features

This crate by default marks all functions as #[inline(always)], which ensures extreme performance in desired cases. However, if code size is still a concern, you can disable this behaviour by disabling the default features, which inlines like normal crates.

Dependencies

~0.4–5.5MB
~11K SLoC