5

When using pre-coroutine asio interface I could dispatch an async function in the current execution context (main function) and let it finish in the ctx.

#include <iostream>
#include <asio.hpp>
using namespace std;
using namespace literals;

int main() {
    asio::io_context ctx;
    asio::steady_timer t(ctx, 2s);
    asio::dispatch([&](){
        cout << "inside coro before wait" << endl;
        t.async_wait([](asio::error_code){
            cout << "inside coro after wait" << endl;
        });
    });
    cout << "starting ctx.run()" << endl;
    ctx.run();
}
$ ./sol
inside coro before wait
starting ctx.run()
inside coro after wait

I would like to replicate this with asio::awaitable but dispatch doesn't support coroutines.

On the other hand co_spawn only adds the coroutine to the context, and doesn't initiate it immediately.

I could use ctx.poll()/ctx.run_one() but the coroutine would have to be the only one ready to execute or at least the first one.

Anwser

If I'm not mistaken the only way to start a coroutine (from outside of coroutine) is using asio::co_spawn(executor, awaitable, token). In contrast to dispatch, co_spawn takes an executor as an argument, where as @sehe said dispatch uses associated executor. So to answer my question on how to start a coroutine immediately is to run it in asio::system_executor().

#include <bits/stdc++.h>
#include <asio.hpp>
using namespace std;
using namespace literals;

int main() {
    asio::io_context ctx;
    asio::co_spawn(asio::system_executor(), [&]() -> asio::awaitable<void> {
        cout << "inside coro before wait" << endl;
        asio::steady_timer t(ctx, 2s);
        co_await t.async_wait(asio::use_awaitable);
        cout << "inside coro after wait" << endl;
        co_return;
    }, asio::detached);
    cout << "starting ctx.run()" << endl;
    ctx.run();
}
$ ./sol
inside coro before wait
starting ctx.run()
inside coro after wait
Tellegar
  • 81
  • 7

1 Answers1

2

There's a myriad ways in which this code won't do what it suggests/you describe it will do:

asio::dispatch([](){
    // executes immediately
    asio::steady_timer t(ctx, 2s);
    t.async_wait([](){
        // executes in ctx.run()
    });
});
ctx.run();
  • It will never wait 2s, because t's destructor will cancel the async_await immediately after being initiated.

  • ctx is apparently a static or a global, because it isn't captured

  • the completion handler doesn't have the required signature

  • even if it did correctly accept an error_code variable, it would not "execute[s] in ctx.run()" because the dispatch dispatches on the associated executor of the lambda. This, because nothing else is associated, will default to a default-constructed instance of asio::system_executor.

    See e.g. Which io_context does std::boost::asio::post / dispatch use?

Q. On the other hand asio::co_spawn only adds the coroutine to the context, and doesn't initiate it immediately.

I don't think that's accurate either. As you mention the code you give is invalid (because post doesn't support coroutines). When fixed to use co_spawn, we can show it to run inside ctx.run():

#include <boost/asio.hpp>
#include <iostream>
namespace asio = boost::asio;
using namespace std::chrono_literals;

int main() {
    asio::io_context ctx;
    asio::co_spawn(
        ctx,
        []() -> asio::awaitable<void> {
            auto ex = co_await asio::this_coro::executor;
            asio::steady_timer t(ex, 2s);
            co_await t.async_wait(asio::use_awaitable);
            std::cout << "inside ctx.run()" << std::endl;
            // co_return;
        },
        asio::detached);

    std::cout << "before ctx.run()" << std::endl;
    ctx.run();
    std::cout << "after ctx.run()" << std::endl;
}

Prints

enter image description here

I really think 99% of the question hinges on these false premises, so I'll await your reaction before expounding.

UPDATE

Thinking longer I think you might be having a variation on this question: asio How to change the executor inside an awaitable?, but without realizing that you had unwittingly been using the system context for the dispatch.

Here's a more comprehensive illustration of precisely how (associated) executors interact with coroutine resume: full size

enter image description here

NOTE also how the work guard is required to avoid ctx running out of work while work is only pending on the system context.

I strongly recommend against relying on the system context, but this illustration should help you connect all the dots?

sehe
  • 374,641
  • 47
  • 450
  • 633
  • Thinking longer I think you might be having a variation on [this question](https://stackoverflow.com/questions/71987021/asio-how-to-change-the-executor-inside-an-awaitable/71991876#71991876), but without realizing that you had unwittingly been using the system context for the dispatch. – sehe Mar 22 '23 at 02:25
  • Here's a more comprehensive illustration of precisely how (associated) executors interact with coroutine resume: https://imgur.com/a/cwCWq0n - note also how the work guard is required to avoid `ctx` running out of work while work is **only** pending on the system context. I strongly recommend against relying on the system context, but this illustration should help you connect all the dots? – sehe Mar 22 '23 at 02:38
  • 1
    What I actually wanted is to change the execution context of the awaitable and start it in the system_executor, thank you for clarifying things – Tellegar Mar 22 '23 at 22:17