This wouldn't do what you expect it to do.
For example, with a single async_accept
loop, you would never reach the point of "no other work to do".
Likewise, if only a single party owns outstanding work<>
(docs and why) there will never be a situation where there is "no other work to do".
Basically, what you really want to do is to chain the polling:
template <typename Condition, typename Handler, typename Executor>
void run_when(Executor& ex, Condition c, Handler h) {
struct Op {
Executor& _executor;
Condition _cond;
Handler _handler;
Op(Executor& ex, Condition c, Handler h)
: _executor(ex), _cond(std::move(c)), _handler(std::move(h))
{ }
void operator()() const {
if (_cond())
_handler(error_code{});
else
ba::post(_executor, std::move(*this));
}
};
ba::post(ex, Op{ex, std::move(c), std::move(h)});
}
This can be used like:
run_when(io,
[&] { return bool(triggered); },
[](error_code ec) { std::clog << "triggered: " << ec.message() << "\n"; });
Demo
Live On Coliru
#include <boost/asio.hpp>
namespace ba = boost::asio;
using boost::system::error_code;
using namespace std::chrono_literals;
template <typename Condition, typename Handler, typename Executor>
void run_when(Executor& ex, Condition c, Handler h) {
struct RunWhen {
Executor& _executor;
Condition _cond;
Handler _handler;
RunWhen(Executor& ex, Condition c, Handler h)
: _executor(ex), _cond(std::move(c)), _handler(std::move(h))
{ }
void operator()() const {
if (_cond())
_handler(error_code{});
else
ba::post(_executor, std::move(*this));
}
};
ba::post(ex, RunWhen{ex, std::move(c), std::move(h)});
}
#include <iostream>
int main() {
// some state that gets changed in the background
std::atomic_bool triggered { false };
std::thread([&] {
std::this_thread::sleep_for(1.5s);
triggered = true;
}).detach();
ba::io_context io;
// just some background polling that shall not block other work
run_when(io, [&] { return bool(triggered); }, [](error_code ec) { std::clog << "triggered: " << ec.message() << "\n"; });
io.run_for(3s);
}
Prints (after ~1.5s):
triggered: Success
BONUS
Why does our handler take an error_code? Well, in line with other Asio operations, you might want to be able to cancel them. Either you make the caller responsible for extending the lifetime of the the run_when<>(...)::Op
instance, complicating life. Or you make it so the Condition
calleable can return a code indicating whether the condition was satisfied or the wait was abandoned¹:
Live On Coliru
#include <boost/asio.hpp>
namespace ba = boost::asio;
using boost::system::error_code;
using boost::system::system_error;
using ba::error::operation_aborted;
using namespace std::chrono_literals;
template <typename Condition, typename Handler, typename Executor>
void run_when(Executor& ex, Condition c, Handler h) {
struct Op {
Executor& _executor;
Condition _cond;
Handler _handler;
Op(Executor& ex, Condition c, Handler h)
: _executor(ex), _cond(std::move(c)), _handler(std::move(h))
{ }
void operator()() const {
try {
if (_cond())
_handler(error_code{});
else
ba::post(_executor, std::move(*this));
} catch(system_error const& se) {
_handler(se.code());
}
}
};
ba::post(ex, Op{ex, std::move(c), std::move(h)});
}
#include <random>
auto random_delay() {
static std::mt19937 engine(std::random_device{}());
return (std::uniform_int_distribution<>(1,2)(engine)) * 1s;
}
#include <iostream>
int main() {
// some state that gets changed in the background
std::atomic_bool triggered { false }, canceled { false };
std::thread([&] { std::this_thread::sleep_for(1.5s); triggered = true; }).detach();
// add a randomized cancellation
{
auto cancel_time = random_delay();
std::clog << "hammer time: " << (cancel_time/1.0s) << "s\n";
std::thread([&] { std::this_thread::sleep_for(cancel_time); canceled = true; }).detach();
}
ba::io_context io;
// just some background polling that shall not block other work
auto condition = [&] { return canceled? throw system_error(operation_aborted) : bool(triggered); };
run_when(io, condition, [](error_code ec) { std::clog << "handler: " << ec.message() << "\n"; });
io.run_for(3s);
}
Which prints either
hammer time: 1s
handler: Success
or
hammer time: 2s
handler: Success
depending on the value of random_delay()
.
¹ (or that the mayor's daughter had a divorce, because error_code is pretty versatile like that)