Boost.ASIO coroutines. Scheduler.
Для реализации примитивов синхронизации короутин (например sequence_barrier) нужен механизм для приостановки короутины до следующей итерации цикла событий. По сути это аналог функции pthread_yield для синхронного кода.
Пример использования выглядит как-то так:
do {
co_await schedule(executor);
do_some_thing();
} while(condition);
Типовая реализация:
struct Awaiter
{
boost::asio::any_io_executor executor;
bool await_ready() { return false; }
void await_suspend(std::coroutine_handle<> handle) {
boost::asio::post(executor, [executor, handle]()
{
handle.resume();
});
}
void await_resume() {}
}
Awaiter schedule(boost::asio::any_io_executor executor) {
return Awaiter{executor};
}
Но для короутин boost::asio::awaitable это не подходит. Поэтому сделаем реализацию средствами Boost, используя функцию boost::asio::async_initiate инстанционирующую специализацию класса boost::asio::async_result для use_awaitable.
inline auto schedule(boost::asio::any_io_executor executor)
{
auto initiate = [executor]<typename Handler>(Handler&& handler) mutable
{
boost::asio::post(executor, [handler = std::forward<Handler>(handler)]() mutable
{
handler();
});
};
return boost::asio::async_initiate<
decltype(boost::asio::use_awaitable), void()>(
initiate, boost::asio::use_awaitable);
}
В async_initiate передается функтор (выполняющий планирование пробуждения и вызова обработчика завершения) и экземпляр use_awaitable. Аналогичным образом реализована поддержка короутин у таймеров и сокетов. Тест:
TEST(schedule_asio, _) {
bool reachedPointA = false;
bool reachedPointB = false;
auto process = [&]() -> boost::asio::awaitable<void> {
reachedPointA = true;
boost::asio::any_io_executor executor = co_await boost::asio::this_coro::executor;
co_await schedule(executor);
reachedPointB = true;
co_return;
};
auto check = [&]() -> boost::asio::awaitable<void> {
EXPECT_TRUE(reachedPointA);
EXPECT_FALSE(reachedPointB);
co_return;
};
auto task = [&]() -> boost::asio::awaitable<void> {
using namespace boost::asio::experimental::awaitable_operators;
co_await(process() && check());
co_return;
};
boost::asio::io_context ioContext;
boost::asio::co_spawn(ioContext, task(), boost::asio::detached);
ioContext.run();
EXPECT_TRUE(reachedPointA);
EXPECT_TRUE(reachedPointB);
}