Reputation: 5069
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'
Upvotes: 3
Views: 106
Reputation: 17073
You can just do it. The main problem with your code is that you defined callback_t
to hold a regular function, not a coroutine. Make it match the signature of target_co_routine
.
using callback_t = std::function<boost::asio::awaitable<void>(int)>;
Then switch to the "preferred" definition of turn()
and address the remaining syntax errors. (The lambda has the wrong signature now, but it's no longer needed. The return value of my_pdu.turn()
cannot be ignored, so add co_await
. The first parameter to my_coroutine
is unused, so drop it.) I also dropped the second parameter to my_coroutine
because it is unused and also because demo
is not declared (much less demo::scope_stuff
). Your real code might need that parameter, but in this example, it's just noise.
#include <boost/asio.hpp>
#include <iostream>
struct pdu {
boost::asio::awaitable<void> turn () {
co_await callback(5);
}
using callback_t = std::function<boost::asio::awaitable<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;
}
boost::asio::awaitable<int> my_coroutine() {
std::cout << "In my coroutine" << std::endl;
pdu my_pdu{target_co_routine};
co_await my_pdu.turn();
co_return 5;
}
This compiles, although I did not actually run this code, primarily because there is a limit to how much time I'm willing to invest in filling in missing pieces from the question. The question does not provide a way to execute this code, so I did not execute it. Still, I have reasonable confidence that it will perform as expected.
Upvotes: 1
Reputation: 393674
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.
Upvotes: 4