Runtimes


Back to index

Overview


tokio
A very "batteries included" runtime and async library
async-executor
The runtime behind async-std and smol
embassy
Rust Embedded async executor and HALs
fuchsia-async
Google's Fuchsia OS executor

The smallest runtime: async-task & futures-lite

use std::{future::Future, panic::catch_unwind, thread};

use async_task::{Runnable, Task};
use futures_lite::future;
use once_cell::sync::Lazy;

fn main() {
    let t = spawn(async {
        println!("Hello task!");
    });

    future::block_on(t);
}

fn spawn<F, T>(future: F) -> Task<T>
where
    F: Future<Output = T> + Send + 'static,
    T: Send + 'static,
{
    static queue: Lazy<flume::Sender<Runnable>> = Lazy::new(|| {
        let (tx, rx) = flume::unbounded::<Runnable>();

        thread::spawn(move || {
            while let Ok(runnable) = rx.recv() {
                let _ = catch_unwind(|| runnable.run());
            }
        });

        tx
    });

    let schedule = |runnable| queue.send(runnable).unwrap();
    let (runnable, task) = async_task::spawn(future, schedule);

    runnable.schedule();
    return task;
}

block_on

Basic setup

use parking::Parker;
use waker_fn::waker_fn;

pub fn block_on<T>(future: impl Future<Output = T>) -> T {
    crate::pin!(future);

    thread_local! {
        // Cached parker and waker for efficiency.
        static CACHE: RefCell<(Parker, Waker)> = RefCell::new(parker_and_waker());
    }

    // ...
}

No block_on nesting…

match cache.try_borrow_mut() {
    // Use the cached parker and waker.
    Ok(cache) => {
        let (parker, waker) = &*cache;
        let cx = &mut Context::from_waker(&waker);

        loop {
            match future.as_mut().poll(cx) {
                Poll::Ready(output) => return output,
                Poll::Pending => parker.park(),
            }
        }
    }
    _ => todo!()
}

Yes block_on nesting

match cache.try_borrow_mut() {
    // Looks like this is a recursive `block_on()` call.
    Err(_) => {
        let (parker, waker) = parker_and_waker();
        let cx = &mut Context::from_waker(&waker);

        loop {
            match future.as_mut().poll(cx) {
                Poll::Ready(output) => return output,
                Poll::Pending => parker.park(),
            }
        }
    }
    _ => todo!()
}

Oh and a smol other thing

  • Parker comes from the parking crate
  • Waker is part of core::task
    • waker_fn is a convenience builder crate for Waker
fn parker_and_waker() -> (Parker, Waker) {
    let parker = Parker::new();
    let unparker = parker.unparker();
    let waker = waker_fn(move || {
        unparker.unpark();
    });
    (parker, waker)
}

We just made a runtime!

Isn't this cool??

fn main() {
    // allow us to attach futures to our state machine after it
    // started running
    let t = spawn(async {
        println!("Hello task!");
    });

    // blocks the current thread until all futures are done
    block_on(t);
}

Anyway

async-std

We can easily create tasks without the syntactic sugar

use async_std::task;

async fn hello() {
    println!("Hello world!");
}

fn main() {
    task::block_on(hello());
}

tokio

We can easily create tasks without the syntactic sugar

use tokio::{runtime::Runtime, task};

async fn hello() {
    println!("Hello world!");
}

fn main() {
    let rt = Runtime::new().unwrap();
    task::block_in_place(move || {
        let local = task::LocalSet::new();
        local.block_on(&rt, hello());
    })
}

Back to index