I got a processing class that signals a certain condition via a callback that is bound late at runtime. The processing class is executed by a coroutine, and the signal wants to call a coroutine eventually too.
struct pdu {
void turn() {
callback(5);
}
/* Not possible due to std::function but preferred
boost::asio::awaitable<void> turn () {
co_await callback(5);
}
*/
using callback_t = std::function<void(int)>;
pdu (callback_t callback) : callback(callback) {}
callback_t callback;
};
boost::asio::awaitable<void> target_co_routine(int x) {
std::cout << "In target coroutine with parameter " << x << std::endl;
co_return;
}
/*!
* @param strand that this coroutine is already running on
*/
boost::asio::awaitable<int> my_coroutine(boost::asio::strand<boost::asio::io_context::executor_type>& strand,std::shared_ptr<demo::scope_stuff>) {
std::cout << "In my coroutine" << std::endl;
pdu my_pdu{[&strand](int x){
//Not feasible since the strand is already blocked.
// Can neither release/defer as it would break the order of execution nor use a dispatch semantic
auto f = boost::asio::co_spawn(strand,target_co_routine(x),boost::asio::use_future);
f.get();
}};
my_pdu.turn();
co_return 5;
}
So turn()
which could be a coroutine, but is not since std::function
can not be co_await
ed, is called by a coroutine my_coroutine
, executed on the strand.
This function calls the callback
, which in turn wants to co_await
a coroutine target_co_routine
eventually, which shall be executed on the same strand, before turn
returns.
So is there any type for using callback_t = std::function<void(int)>;
or boost::asio
functionality, i.e. a different call like the boost::asuo::co_spawn
call that lets me achieve this?
I tried to await naive on the std::function but got an error:
error: no matching member function for call to 'await_transform'
You can make a coroutine signature by replacing void
with awaitable<void>
:
#include <boost/asio.hpp>
#include <iostream>
namespace asio = boost::asio;
using Void = asio::awaitable<void>;
struct pdu {
Void turn() { co_await callback_(5); }
using callback_t = std::function<Void(int)>;
pdu(callback_t callback) : callback_(std::move(callback)) {}
private:
callback_t callback_;
};
Void target_co_routine(int x) {
std::cout << "In target coroutine with parameter " << x << std::endl;
co_return;
}
namespace demo {
struct scope_stuff {};
} // namespace demo
asio::awaitable<int> my_coroutine(asio::any_io_executor strand, std::shared_ptr<demo::scope_stuff>) {
std::cout << "In my coroutine" << std::endl;
pdu my_pdu{
[strand](int x) -> Void { co_await co_spawn(strand, target_co_routine(x), asio::deferred); }};
co_await my_pdu.turn();
co_return 5;
}
int main() {
asio::thread_pool ioc;
co_spawn( //
ioc, //
my_coroutine(make_strand(ioc), //
std::make_shared<demo::scope_stuff>()),
asio::detached);
ioc.join();
}
Printing
In my coroutine
In target coroutine with parameter 5
The more general approach is to write async operations e.g. using async_initiate
or async_compose
which gives the caller the choice of completion token, be it use_awaitable
, use_future
, detached
or anything else.
Also, do not pass executors by reference. Executors are lightweight "references" to execution contexts.