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

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