Boost logo

Boost :

Subject: Re: [boost] library to support async/await pattern
From: Hartmut Kaiser (hartmut.kaiser_at_[hidden])
Date: 2013-04-21 15:54:09

> Is there interest for a coroutine-based library to make asynchronous APIs
> easier to deal with?
> Typically, for each task there is cruft: chaining callbacks, managing
> intermediate state and error codes (since exceptions don't fit this
> model).
> The code flow get inverted and becomes difficult to follow.
> Recent versions of F# and C# solve this problem. They implement an await
> operator that effectively suspends the executing method until a task
> completes. The compiler takes care of transforming subsequent code into a
> continuation. Everything runs on the main thread, with asynchronous
> methods spending most time awaiting. N3328 proposes resumable functions of
> this kind in C++.
> For an immediate solution we could leverage the Boost.Context/Coroutine
> library. The resulting code may look like this:
> try {
> task = do_a_async(...)
> // yield until task done
> task.await();
> } catch (const some_exception& e) {
> // exceptions arrive in awaiting context
> }
> // normal code flow
> for (auto& task : tasks1) {
> task.await();
> }
> taskAny = await_any(tasks2);
> taskAny.await();
> ...

FWIW, HPX provides all this and more
( It's well aligned with the
Standard's semantics and exposes an interface very close to what you're
showing above. As a bonus all of this is available in distributed scenarios
as well (remote thread scheduling and synchronization).

> There needs to be a representation for Awaitable tasks (similar to
> std::future but non-blocking). The other requirement is to have a
> Scheduler (run loop) in order to weave between coroutines.

We strongly believe that we don't need a new construct for this. Threads and
futures is exactly the abstraction to be used for this as well. In HPX,
hpx::thread (full semantic equivalence to std::thread, except it represents
a task) and hpx::future expose semantics similar to those you're describing:

    hpx::future<int> f = hpx::async([](){ return 42; });
    BOOST_ASSERT(f.get() == 42);

Additionally HPX implements N3558 (A Standardized Representation of
Asynchronous Operations,, allowing
to write:

    hpx::future<int> f1 = hpx::async([](){ return 42; });
    hpx::future<int> f2 = hpx::async([](){ return 43; });

    tuple<hpx::future<int>, hpx::future<int> > result =
        hpx::wait_all(f1, f2).get();

    BOOST_ASSERT(get<0>(result).get() == 42);
    BOOST_ASSERT(get<1>(result).get() == 43);


All of this does not block execution, but resumes the underlying tasks
allowing to perform other work while a task is suspended. The context switch
overhead is in the range of ~200-400ns, the full management overhead for one
task (creation, scheduling, execution, and deletion) is in the range of

Regards Hartmut

> Benefits:
> - normal code flow: plain conditionals, loops, exceptions, RAII
> - algorithm state tracked on coroutine stack
> - async tasks are composable
> - any async API can be wrapped
> Cons:
> - must wrap async APIs (e.g. Boost.Asio)
> - needs std::exception_ptr to dispatch exceptions
> - stackful coroutines are sometimes difficult to debug
> I wrote an open-source library that does this:
> It's far from Boost style but the concept looks sane. For a comparison
> between async patterns please see:
> Making a Boost version would involve serious redesign. So is this worth
> pursuing?

Boost list run by bdawes at, gregod at, cpdaniel at, john at