Async/Await

The Problem With Processes

  • Processes are a great concurrency / parallelism mechanism

    • Fantastic isolation of control flow and data
    • Simple semantics
    • OS deals with them well
  • But processes are inefficient

    • Inter-process communication is inefficient
    • Memory usage tends toward inefficiency
  • So threads

The Problem With Threads

  • Threads are a decent concurrency / parallelism mechanism

    • Control flow isolation
    • Manageable semantics
    • OS deals with them OK
    • Way more efficient than processes
  • But threads are still inefficient

    • Context switch costs
    • Stack per thread
    • Inter-thread locking

Task Pools

  • Idea: Small number of threads executing large number of short tasks

    • Minimize context switch costs
    • Still stack per thread, but not many of those
    • Synchronization can be done with task rather than locks
  • Mechanism: Task Pool — a collection of ready-to-run tasks

    • Worker fetches task from pool and executes it until
      • Task finishes
      • Task is going to block — put it aside after arranging for it to be put back in the pool when it can proceed (not easy)

Futures

  • Old idea: a closure that will "continue running" a computation when called

  • Futures eventually deliver a value that is the result of computation

  • Compare with delay/force, promises, generators, continuations, etc

  • Represent computation to be performed in task pool as a future

std::future::Future

  • Odd Rust implementation of future idea

  • New trait

    pub trait Future {
        type Output;
        fn poll(
            self: Pin<&mut Self>,
            cx: &mut Context,
        ) -> Poll<Self::Output>;
    }
    
  • Ignore the Pin and Context

  • When poll() is called on a Future, it will return either Poll::Pending or Poll::Ready(val)

  • If poll() returns Poll::Pending, it will also arrange for the future to wake up via callback when it can proceed with the computation

async/await

  • Can declare a function or block async

  • If the result type of the function or block was T it is now impl Future<Output=T>

  • The compiler builds a custom implementation of Future that "does the right thing" in this situation

  • If you evaluate such a function or block with .await, it will poll until it gets a ready value and then it will return that value

Rust's async/await Ecosystem

  • Need a few things:

    • Implementations of potentially-blocking primitives that are async

    • Task pool implementation that provides machinery for spawning and supplying worker threads

  • Currently provided by either the tokio ecosystem or the async-std ecosystem

  • Idea of std::future::Future is that you can use the primitives with either ecosystem (or build your own machinery, or whatever)

Pick Your Ecosystem

  • Tokio

    • Older: lots more experience with
    • Firefox thing
    • Lots of backward compatibility adventures
  • async-std

    • Simpler, cleaner, newer interface
    • Easier to get started with

async/await Maturity

  • All of this is quite immature (after many years)

    • Docs aren't done
    • Things are changing fairly rapidly
    • Big chunks are missing
  • The Colored Function Problem is real

Example

http://github.com/pdx-cs-rust/countserver

Last modified: Monday, 9 March 2020, 1:31 PM