1

I am being confused the asio::timer coroutine with completion handler "use_awaitable" does not work if I modify the timer in another coroutine.

The "Leave foo" never reached:

Enter foo
Enter bar
Leave bar
The End

Below is code:

static asio::awaitable<void> foo(asio::steady_timer& timer)
{
    cout << "Enter foo" << endl;
    timer.expires_from_now(asio::steady_timer::clock_type::duration::max());
    co_await timer.async_wait(asio::use_awaitable);
    cout << "Leave foo" << endl;
}

static asio::awaitable<void> bar(asio::steady_timer& timer)
{
    cout << "Enter bar" << endl;
    sleep(2); // wait a little for asio::io_service::run to be executed
    timer.expires_after(asio::chrono::seconds(1));
    cout << "Leave bar" << endl;
    co_return;
}

int main()
{
    asio::io_context ioService;
    asio::steady_timer timer(ioService);

    asio::co_spawn(ioService, foo(timer), asio::detached);
    asio::co_spawn(ioService, bar(timer), asio::detached);

    ioService.run();
    std::printf("The End\n");
    return 0;
}

Actually I just want to suspend a coroutine and resume it in another coroutine over asio/c++20 without any other threads.

sehe
  • 374,641
  • 47
  • 450
  • 633
wk1999
  • 13
  • 3
  • "*I just want to suspend a coroutine and resume it in another coroutine*" Then you need to get one coroutine to know about the other one. The standard way to do that is for one coroutine to be the invoker of the other. – Nicol Bolas Mar 06 '23 at 03:21
  • Hi @NicolBolas, thanks for your reply, but I am not quite sure I got your point. I have two socket servers in a same io context, one is suspending until the other receives a certain signal to resume it. This is my case. Does it mean it is impossible? – wk1999 Mar 06 '23 at 05:37

1 Answers1

0

The docs state that expires_after cancels any pending async wait. Using BOOST_ASIO_ENABLE_HANDLER_VIZ:

enter image description here

You don't handle the exception. If you did, you would notice it happening:

static asio::awaitable<void> foo(asio::steady_timer& timer) try {
    std::cout << "Enter foo" << std::endl;
    timer.expires_from_now(asio::steady_timer::clock_type::duration::max());
    co_await timer.async_wait(asio::use_awaitable);
    std::cout << "Leave foo" << std::endl;
} catch (boost::system::system_error const& se) {
    std::cout << "Error: " << se.what() << std::endl;
}

Now the output is

Enter foo
Enter bar
Leave bar
Error: Operation canceled [system:125]
The End

Alternatively you might use error-codes:

boost::system::error_code ec;
co_await timer.async_wait(redirect_error(asio::use_awaitable, ec));
std::cout << "Leave foo (" << ec.message() << ")" << std::endl;

Or even:

auto [ec] = co_await timer.async_wait(as_tuple(asio::use_awaitable));
std::cout << "Leave foo (" << ec.message() << ")" << std::endl;

Both printing (Live):

Enter foo
Enter bar
Leave bar
Leave foo (Operation canceled)
The End

More Problems / Ideas

There's a problem with sleep-ing in a coro. It block the execution context, so no asynchronous work will be able to complete. That's certainly not what you want.

Besides, it looks a lot like you are trying to coordinate execution of two coroutines. If you want "signal-like" behaviour, then by all means, this can work as long as you correctly handle the error-codes as well.

If you just want to coordinate cancellation, use the cancellation_slot facility. In fact, there exists pretty powerful syntactic sugar for binding shared cancellation slots across asio::awaitable<> instances:

Live On Coliru

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

static void trace(char const* msg) {
    static constexpr auto now = std::chrono::steady_clock::now;
    static auto const start = now();
    std::cout << std::setw(4) << (now() - start) / 1ms << "ms " << msg << std::endl;
}

static asio::awaitable<void> async_simulate_work(auto delay) {
    co_await asio::steady_timer(co_await asio::this_coro::executor, delay)
        .async_wait(asio::use_awaitable);
}

static asio::awaitable<void> foo() try {
    trace("Foo enter");
    for (;;) {
        trace("Foo working...");
        co_await async_simulate_work(100ms);
    }
    trace("Foo leave");
} catch (boost::system::system_error const& se) {
    trace("Foo cancel");
}

static asio::awaitable<void> bar() {
    trace("Bar enter");
    co_await async_simulate_work(260ms);
    trace("Bar leave");
}

int main() {
    asio::io_context   ioService;
    asio::steady_timer timer(ioService);

    asio::co_spawn(ioService, foo() || bar(), asio::detached);

    ioService.run();
    trace("The End");
}

Printing e.g.

    g++ -std=c++20 -O2 -Wall -pedantic -pthread main.cpp && ./a.out
g++ -std=c++20 -O2 -Wall -pedantic -pthread main.cpp && ./a.out
   0ms Foo enter
   0ms Foo working...
   0ms Bar enter
 100ms Foo working...
 200ms Foo working...
 260ms Bar leave
 260ms Foo cancel
 260ms The End
sehe
  • 374,641
  • 47
  • 450
  • 633