await in c++ - a series
I have seen a few articles that introduce the await proposal as implemented in Visual Studio 2015 Preview. In this series I will share my exploration of the await feature by showing how I built async_generator<T>
and some algorithms and adaptors I built to use it. To skip the exposition, go straight to the code on github.
A Look Ahead
auto test = []() -> std::future<void> {
for __await(auto&& rt :
// tick every second
as::schedule_periodically(clk::now() + 1s, 1s,
[](int64_t tick) {return tick; }) |
// ignore every other tick
ao::filter([](int64_t t) { return (t % 2) == 0; }) |
// stop after 10 ticks
ao::take(10)) {
// the loop body will be run every other second - 10 times
std::cout << "for " << std::this_thread::get_id()
<< " - " << rt
<< std::endl;
}
};
// this function will return immediately before any text is printed
auto done = test();
// block until the loop has finished
done.get();
This looks similar to Eric Niebler’s Range proposal (GitHub, Blog), but these are async ranges. Not only are the types involved different, but also the for loop and the algorithms. Coordinating many Ranges from many threads over time has additional complexity and different algorithms. The ReactiveExtensions family of libraries provide a lot of algorithms useful for async Ranges. The RxMarbles site has live diagrams for many of the algorithms. rxcpp implements some of these algorithms in C++ without await and the series will produce adaptors that allow async Ranges and rxcpp Observables to be interchanged.
Types in Time
Time is what the await proposal introduces into the C++ language. This is a table of types that represent each combination of values and time.
Value | Sequence | |
---|---|---|
past | T |
vector<T> |
lazy | []() -> T { . . . } |
generator<T> |
later | future<T> |
async_generator<T> |
- past - A value has already been produced before the caller asks for it.
- lazy - A value is produced when asked for by a caller.
- later - When a value is produced the caller is resumed.
The three that I will explore in the context of await are future<T>
, generator<T>
and async_generator<T>
.
future<T>
- the value arrives Later
This is covered first because yield_value is composed on top of await. future<T>
represents a value of T that may become available later. It may hold an exception instead. A function that returns future<T>
is allowed to use await
on an awaitable type.
generator<T>
- each value is Lazy
generator<T>
implements the Range Concept so it works with existing algorithms from STL and Rangev3 and the range-for
feature. It can be found in the header experimental/generator
. A function that returns generator<T>
is allowed to use the yield_value
keyword (which evaluates to await generator<T>::promise_type::yield_value(T)
).
async_generator<T>
- each value arrives Later
async_generator<T>
implements a new AsyncRange Concept. begin()
and ++iterator
return an awaitable type that produces an iterator later while end()
returns an iterator
immediately. A new set of algorithms is needed and a new async-range-for
has been added that inserts await
into i = await begin()
and await ++i
. A function that returns async_generator<T>
is allowed to use await
and yield_value
.
Resources
- Gor Nishanov kindly answered many emails as I worked on the code. His epic presentation (PDF, YouTube) of the design, implementation and sample usage at CPPCON 2014 is required watching.
- Visual Studio 2015 Preview implements the await proposal (in these articles I am using CTP6).
- Visual Studio Team Blog Post introducing the await implementation.
- Paolo Severini another nice introduction to await.