Runtimes, Wakers, and the Reactor-Executor Pattern 198 let mut future = match self. get_future(id) { Some(f) => f, // guard against false wakeups None => continue, }; let waker = self. get_waker(id); match future. poll(&waker) { Poll State::Not Ready => self. insert_task(id, future), Poll State::Ready(_) => continue, } } let task_count = self. task_count(); let name = thread::current(). name(). unwrap_or_default(). to_string(); if task_count > 0 { println!("{name}: {task_count} pending tasks. Sleep until notified. "); thread::park(); } else { println!("{name}: All tasks are finished"); break; } } } } block_on will be the entry point to our Executor. Often, you will pass in one top-level future first, and when the top-level future progresses, it will spawn new top-level futures onto our executor. Each new future can, of course, spawn new futures onto the Executor too, and that's how an asynchronous program basically works. In many ways, you can view this first top-level future in the same way you view the main function in a normal Rust program. spawn is similar to thread::spawn, with the exception that the tasks stay on the same OS thread in this example. This means the tasks won't be able to run in parallel, which in turn allows us to avoid any need for synchronization between tasks to avoid data races. Let's go through the function step by step: 1. The first thing we do is spawn the future we received onto ourselves. There are many ways this could be implemented, but this is the easiest way to do it. 2. Then, we have a loop that will run as long as our asynchronous program is running.