Wait for timer in other coroutine (Asio)

When using asio::spawn, it possible to wait for a timer in a separate coroutine? E.g., in the code below I want coroutine 2 started to print to the console and then, 5 seconds later, coroutine 2 finished.

#include <iostream>
#include <boost/asio.hpp>
#include <boost/asio/spawn.hpp>

int main() {

  boost::asio::io_context io;

  // coroutine 1
  boost::asio::spawn(io, [&](boost::asio::yield_context yield) {
    boost::asio::deadline_timer timer(io, boost::posix_time::seconds(5));
    timer.async_wait(yield);
  });

  // coroutine 2
  boost::asio::spawn(io, [&](boost::asio::yield_context yield) {
    std::cout << "coroutine 2 started" << std::endl;
    // wait for coroutine 1 timer to finish
    std::cout << "coroutine 2 finished" << std::endl;
  });

  io.run();
}

Answer

Many ways, most would center on sharing the timer.

Easiest way I can think of is by spawning coro2 from coro1 (“forking”, if you will), because why not:

Live On Wandbox

#include <boost/asio.hpp>
#include <boost/asio/spawn.hpp>
#include <iostream>

using namespace std::chrono_literals;
using boost::asio::yield_context;
using timer = boost::asio::steady_timer;

static auto now = timer::clock_type::now;

static void coro_sleep(timer::duration delay, yield_context yc) {
    timer(get_associated_executor(yc), delay)
        .async_wait(yc);
}

int main() {
    static constexpr auto never = timer::time_point::max();

    static auto logger = [](auto name) {
        return [name, start = now()](auto const&... args) {
            ((std::cout << name << "t+" << (now() - start) / 1ms << "mst") << ... << args) << std::endl;
        };
    };

    boost::asio::io_context ctx;
    spawn(ctx, [log = logger("coro1")](yield_context yield) {
        log("started");
        timer cond(get_associated_executor(yield), 5s);

        spawn(yield, [&cond, log = logger("coro2")](yield_context yield) {
            log("started");

            cond.async_wait(yield);
            log("condition met");

            coro_sleep(1200ms, yield);
            log("signal done");
            cond.expires_at(never);

            log("exiting");
        });

        for (; cond.expiry() != never; coro_sleep(1s, yield)) {
            log("alive");
        }

        log("exiting");
    });

    ctx.run();
}

Prints

coro1   +0ms    started
coro2   +0ms    started
coro1   +0ms    alive
coro1   +1001ms alive
coro1   +2001ms alive
coro1   +3001ms alive
coro1   +4001ms alive
coro2   +5000ms condition met
coro1   +5002ms alive
coro1   +6002ms alive
coro2   +6200ms signal done
coro2   +6200ms exiting
coro1   +7002ms exiting

Of course there are many ways to share the timer if you so require, but the principle will be the same. Pay attention to synchronization of shared resources when you are using multiple threads to run the execution context(s).

The way I phrased it, a simple change would make sure of this:

Live On Wandbox

boost::asio::thread_pool ctx;
spawn(make_strand(ctx), [log = logger("coro1")](yield_context yield) {