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
(https://github.com/STEllAR-GROUP/hpx/). 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,
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2013/n3558.pdf), 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);

etc.

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
~700ns.

Regards Hartmut
---------------
http://boost-spirit.com
http://stellar.cct.lsu.edu

> 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:
> https://github.com/vmilea/CppAwait.
>
> It's far from Boost style but the concept looks sane. For a comparison
> between async patterns please see:
> https://github.com/vmilea/CppAwait/blob/master/Examples/ex_stockClient.cpp
>
> Making a Boost version would involve serious redesign. So is this worth
> pursuing?


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk