Reputation: 41820
I'm currently working on a project that uses boost asio for networking. I want to implement a function that look like this:
template<command T, response R = typename T::response_type>
auto send_command(T c) -> boost::asio::awaitable<R> {
// ...
}
In my code, when I'm writing the command to the socket, I will eventually receive a response with the result. I may receive any number of other responses in the socket before getting the response to a specific command.
I figured I could use async_initiate
to make sending a command an asynchronous operation composed of a write and a read.
I've already used async_initiate
to start asynchronous operations, but it was of the type boost::asio::awaitable<void>
. And the code looked like this:
// returns the awaitable for the coroutine to suspend
return asio::async_initiate<CompletionToken, void(void)>(
[self = shared_from_this()](auto&& handler) {
self->callback_queue.push(std::forward<decltype(handler)>(handler));
}
);
// later
auto& resume = callback_queue.front();
resume(); // resume the suspended coroutine
But I can't figure where to put the return value here when It's not void!
auto const command_id = std::int32_t{...};
auto& resume_command = commands_continuations[command_id];
resume_command(response_result); // Send the `co_await` result in parameters??
I'm simply confused how to send the return of the awaitable and how to initiate the asynchronous operation with a result.
Upvotes: 4
Views: 3125
Reputation: 41820
It turns out that there are a number of fixed signature a async operation can take.
The void(void)
is one, but to have a result, we can simply use void(T)
:
return asio::async_initiate<CompletionToken, void(R)>(
[self = shared_from_this()](auto&& handler) {
self->callback_queue.push(std::forward<decltype(handler)>(handler));
}
);
The handler
will be a callable that takes a R
as parameter and will become the result of the co_await
operation.
To have exception handling, cancellation, I tried two different ways to propagate exceptions. One is to send an std::exception_ptr
and the other was to use boost::system::error_code
. In my case, I choose error code as I'm more comfortable using them.
return asio::async_initiate<CompletionToken, void(boost::system::error_code, R)>(
[self = shared_from_this()](auto&& handler) {
self->callback_queue.push(std::forward<decltype(handler)>(handler));
}
);
The callback take form of a move only function that matches the signature void(boost::system::error_code, R)
. I call it like this:
auto callback = std::exchange(callback_queue[command_id], {});
if (result) {
// resumes the coroutine normally with the result
callback(boost::system::error_code{}, *result);
} else {
// Throws exception in the coroutine
callback(make_error_code(boost::system::errc::operation_canceled), R{});
}
There is more information about it in the documentation about ASIO/Networking TS
Upvotes: 3