c++boost-asiocoroutinec++-coroutine

Coroutine callback late bound i.e. std::function, indirect co_await


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_awaited, 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'


Solution

  • You can make a coroutine signature by replacing void with awaitable<void>:

    Live On Coliru

    #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
    

    Other Notes

    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.