Reputation: 1637
I am currently trying to use the new C++20 coroutines with boost::asio. However I am struggling to find out how to implement custom awaitable functions (like eg boost::asio::read_async). The problem I am trying to solve is the following:
I have a connection object where I can make multiple requests and register a callback for the response. The responses are not guaranteed to arrive in the order they have been requested. I tried wrapping the callback with a custom awaitable however I am unable to co_await this in the coroutine since there is no await_transform for my awaitable type in boost::asio::awaitable.
The code I tried to wrap the callback into an awaitable is adapted from here: https://books.google.de/books?id=tJIREAAAQBAJ&pg=PA457
auto async_request(const request& r)
{
struct awaitable
{
client* cli;
request req;
response resp{};
bool await_ready() { return false; }
void await_suspend(std::coroutine_handle<> h)
{
cli->send(req, [this, h](const response& r)
{
resp = r;
h.resume();
});
}
auto await_resume()
{
return resp;
}
};
return awaitable{this, r};
}
which I tried calling in a boost coroutine like this:
boost::asio::awaitable<void> network::sts::client::connect()
{
//...
auto res = co_await async_request(make_sts_connect());
//...
}
giving me the following error:
error C2664: 'boost::asio::detail::awaitable_frame_base<Executor>::await_transform::result boost::asio::detail::awaitable_frame_base<Executor>::await_transform(boost::asio::this_coro::executor_t) noexcept': cannot convert argument 1 from 'network::sts::client::async_request::awaitable' to 'boost::asio::this_coro::executor_t'
Is there any way to achieve this functionality ?
Upvotes: 11
Views: 2307
Reputation: 1637
I actually managed to find a solution to this. The way to do this is by using boost::asio::async_initiate
to construct a continuation handler and just defaulting the handler to boost::use_awaitable
. As an added bonus this way it is trivial to match it to the other async function by simply using a template argument for the handler.
template<typename ResponseHandler = boost::asio::use_awaitable_t<>>
auto post(const request& req, ResponseHandler&& handler = {})
{
auto initiate = [this]<typename Handler>(Handler&& self, request req) mutable
{
send(req, [self = std::make_shared<Handler>(std::forward<Handler>(self))](const response& r)
{
(*self)(r);
});
};
return boost::asio::async_initiate<
ResponseHandler, void(const response&)>(
initiate, handler, req
);
}
The only problem here is that std::function does not move construct apparently so I had to wrap the handler in a std::shared_ptr
.
Upvotes: 9