Tour
Entry into an async environment
In order to use awaitables we need to be able to co_await
them, i.e. be within a coroutine.
We got four ways to achieve this:
- async/main.hpp
-
replace
int main
with a coroutine
async::main co_main(int argc, char* argv[])
{
// co_await things here
co_return 0;
}
- async/thread.hpp
-
create a thread for the asynchronous environments
async::thread my_thread()
{
// co_await things here
co_return;
}
int main(int argc, char ** argv[])
{
auto t = my_thread();
t.join();
return 0;
}
- async/task.hpp
-
create a task and run or spawn it
async::task<void> my_thread()
{
// co_await things here
co_return;
}
int main(int argc, char ** argv[])
{
async::run(my_task()); // sync
asio::io_context ctx;
async::spawn(ctx, my_task(), asio::detached); // async, refer to async for details
ctx.run();
return 0;
}
Promises
Promises are the recommended default coroutine type. They’re eager and thus easily usable for ad-hoc concurrecy.
async::promise<int> my_promise()
{
co_await do_the_thing();
co_return 0;
}
async::main co_main(int argc, char * argv[])
{
// start the promise here
auto p = my_promise();
// do something else here
co_await do_the_other_thing();
// wait for the promise to complete
auto res = co_wait p;
co_return res;
}
Tasks
Tasks are lazy, which means they won’d do anything before awaited or spwaned.
async::task<int> my_task()
{
co_await do_the_thing();
co_return 0;
}
async::main co_main(int argc, char * argv[])
{
// create the task here
auto t = my_task();
// do something else here first
co_await do_the_other_thing();
// start and wait for the task to complete
auto res = co_wait t;
co_return res;
}
Generator
A generator is the only type in async that can co_yield
values.
Generator are eager by default. Unlike std::generator
the async::generator
can co_await
and thus is asynchronous.
async::generator<int> my_generator()
{
for (int i = 0; i < 10; i++)
co_yield i;
co_return 10;
}
async::main co_main(int argc, char * argv[])
{
// create the generator
auto g = my_generator();
while (g)
printf("Generator %d\n", co_await g);
co_return 0;
}
Values can be pushed into the generator, that will be returned from the co_yield
.
async::generator<double, int> my_eager_push_generator(int value)
{
while (value != 0)
value = co_yield value * 0.1;
co_return std::numeric_limits<double>::quiet_NaN();
}
async::main co_main(int argc, char * argv[])
{
// create the generator
auto g = my_generator(5);
assert(0.5 == co_await g(4)); // result of 5
assert(0.4 == co_await g(3)); // result of 4
assert(0.3 == co_await g(2)); // result of 3
assert(0.2 == co_await g(1)); // result of 2
assert(0.1 == co_await g(0)); // result of 1
// we let the coroutine go out of scope while suspended
// no need for another co_await of `g`
co_return 0;
}
A coroutine can also be made lazy using this_coro::initial
.
async::generator<double, int> my_eager_push_generator()
{
auto value = co_await this_coro::initial;
while (value != 0)
value = co_yield value * 0.1;
co_return std::numeric_limits<double>::quiet_NaN();
}
async::main co_main(int argc, char * argv[])
{
// create the generator
auto g = my_generator(); // lazy, so the generator waits for the first pushed value
assert(0.5 == co_await g(5)); // result of 5
assert(0.4 == co_await g(4)); // result of 4
assert(0.3 == co_await g(3)); // result of 3
assert(0.2 == co_await g(2)); // result of 2
assert(0.1 == co_await g(1)); // result of 1
// we let the coroutine go out of scope while suspended
// no need for another co_await of `g`
co_return 0;
}
join
If multiple awaitables work in parallel they can be awaited simultaneously with join.
async::promise<int> some_work();
async::promise<double> more_work();
async::main co_main(int argc, char * argv[])
{
std::tuple<int, double> res = async::join(some_work(), more_work());
co_return 0;
}
select
If multiple awaitables work in parallel, but we want to be notified if either completes, we shall use select.
async::generator<int> some_data_source();
async::generator<double> another_data_source();
async::main co_main(int argc, char * argv[])
{
auto g1 = some_data_source();
auto g2 = another_data_source();
int res1 = co_await g1;
double res2 = co_await g2;
printf("Result: %f", res1 * res2);
while (g1 && g2)
{
switch(variant2::variant<int, double> nx = co_await async::select(g1, g2))
{
case 0:
res1 = variant2::get<0>(nx);
break;
case 1:
res2 = variant2::get<1>(nx);
break;
}
printf("New result: %f", res1 * res2);
}
co_return 0;
}
select in this context will not cause any data loss.
|