asio How to change the executor inside an awaitable?

You want to associate your executor with the completion token, and then let post/dispatch/defer figure it out from there:

co_await asio::post(bind_executor(pstrand, asio::use_awaitable));

See also e.g. When must you pass io_context to boost::asio::spawn? (C++) or boost::asio::bind_executor does not execute in strand for more details on how it works/why it works/when it works.

It does in fact explain how get_associated_executor does use a default so it might explain how explicitly posting to an executor didn’t seem to work here (I haven’t checked the associated executor implementation for use_awaitable just now)

Here’s my take:

Live On Compiler Explorer

#include <boost/asio.hpp>
#include <boost/asio/experimental/as_single.hpp>
#include <boost/bind/bind.hpp>
#include <coroutine>
#include <iostream>
#include <thread>

inline void tout(auto const& msg) {
    static std::mutex mx;
    std::lock_guard   lk(mx);

    static const std::hash<std::thread::id> h{};

    std::cout << "T" << (h(std::this_thread::get_id()) % 100) << " " << msg
            << std::endl;
}

namespace asio = boost::asio;

asio::awaitable<void> mainCo(asio::io_context& appIO,
                            asio::io_context& prodIO) {
    auto to_app = bind_executor(make_strand(appIO), asio::use_awaitable);
    auto to_prod = bind_executor(make_strand(prodIO), asio::use_awaitable);

    tout("MC on APPIO");
    co_await asio::post(to_prod); tout("MC on PRODIO");
    co_await asio::post(to_app);  tout("MC on APPIO");
    co_await asio::post(to_prod); tout("MC on PRODIO");
    co_await asio::post(to_prod); tout("MC on PRODIO");
    co_await asio::post(to_app);  tout("MC on APPIO");
}

int main() {
    asio::io_context prodIO, appIO;
    auto             prodWork = asio::make_work_guard(prodIO);

    std::thread prodThread{[&prodIO] {
        tout("ProdThread run start");
        prodIO.run(); // if this call is removed the mainCo is stuck as
                    // expected
        tout("ProdThread run done");
    }};

    asio::co_spawn(appIO, mainCo(appIO, prodIO), asio::detached);

    tout("MainThread run start");
    appIO.run();
    tout("MainThread run done");

    prodWork.reset();
    prodThread.join();
}

Prints e.g.

T49 ProdThread run start
T31 MainThread run start
T31 MC on APPIO
T49 MC on PRODIO
T31 MC on APPIO
T49 MC on PRODIO
T49 MC on PRODIO
T31 MC on APPIO
T31 MainThread run done
T49 ProdThread run done

BONUS

I’d suggest passing executors, not execution context references. It’s cleaner and more flexible: https://godbolt.org/z/Tr54vf8PM

It then makes it trivial to replace the prodIO + thread with a simple thread_pool execution context. It removes the need for work guards and also the fixes missing exception handling: https://godbolt.org/z/a3GT61qdh

Leave a Comment