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.
The docs state that expires_after
cancels any pending async wait. Using BOOST_ASIO_ENABLE_HANDLER_TRACKING
:
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
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:
#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