如何等待两个计时器中的任何一个完成(Boost Asio)
How to wait for either of two timers to finish (Boost Asio)
当 timer1
和 timer2
都完成时,下面的代码将打印到控制台。如何更改它以在 timer1
或 timer2
完成时打印, 然后取消另一个计时器。
#include <iostream>
#include <boost/asio.hpp>
#include <boost/asio/spawn.hpp>
int main() {
boost::asio::io_context io;
boost::asio::deadline_timer timer1(io, boost::posix_time::seconds(5));
boost::asio::deadline_timer timer2(io, boost::posix_time::seconds(1));
boost::asio::spawn(io, [&](boost::asio::yield_context yield){
timer1.async_wait(yield);
timer2.async_wait(yield);
std::cout << "Both timer1 and timer2 have finished" << std::endl;
});
io.run();
}
怎么样:
#include <iostream>
#include <boost/asio.hpp>
#include <boost/asio/spawn.hpp>
void print_timer_expired( bool& flag)
{
if( flag )
return;
flag = true;
std::cout << "Timer1 or timer2 has finished" << std::endl;
}
int main() {
boost::asio::io_context io;
bool flag = false; // true if message has been printed
boost::asio::deadline_timer timer1(io, boost::posix_time::seconds(5));
boost::asio::deadline_timer timer2(io, boost::posix_time::seconds(1));
boost::asio::spawn(io, [&](boost::asio::yield_context yield){
timer1.async_wait(yield);
print_timer_expired( flag );
});
boost::asio::spawn(io, [&](boost::asio::yield_context yield){
timer2.async_wait(yield);
print_timer_expired( flag );
});
io.run();
}
我把问题的意思理解为“你好async_wat_any(timer1, timer2, ..., yield)
。
另一个答案在指向回调完成处理程序以提供此功能时是正确的,但它们不提供回到单个协程的胶水。
现在 Asio's async operations 抽象出所有调用样式(回调、use_future、use_awaitable、yield_context 等...)之间的差异 - 将它们全部恢复本质上是在“回调”风格下。
因此,您可以创建自己的异步初始化,将它们联系在一起,草图:
template <typename Token>
auto async_wait_any( std::vector<std::reference_wrapper<timer>> timers, Token token) {
using Result =
boost::asio::async_result<std::decay_t<Token>, void(error_code)>;
using Handler = typename Result::completion_handler_type;
Handler handler(token);
Result result(handler);
for (timer& t : timers) {
t.async_wait([=](error_code ec) mutable {
if (ec == boost::asio::error::operation_aborted)
return;
for (timer& t : timers) {
t.cancel_one();
}
handler(ec);
});
}
return result.get();
}
现在在你的协程中你可以说:
timer a(ex, 100ms);
timer b(ex, 200ms);
timer c(ex, 300ms);
async_wait_any({a, b, c}, yield);
第一个完成后它将 return。
让我们演示一下
此外,使其更通用,而不是对计时器类型进行硬编码。事实上,在 Windows 环境中,您将能够等待可等待对象(如 Event, Mutex, Semaphore) with the same interface:
template <typename Token, typename... Waitable>
auto async_wait_any(Token&& token, Waitable&... waitable) {
using Result =
boost::asio::async_result<std::decay_t<Token>, void(error_code)>;
using Handler = typename Result::completion_handler_type;
Handler completion_handler(std::forward<Token>(token));
Result result(completion_handler);
// TODO use executors from any waitable?
auto ex = get_associated_executor(
completion_handler,
std::get<0>(std::tie(waitable...)).get_executor());
auto handler = [&, ex, ch = completion_handler](error_code ec) mutable {
if (ec != boost::asio::error::operation_aborted) {
(waitable.cancel_one(), ...);
post(ex, [=]() mutable { ch(ec); });
}
};
(waitable.async_wait(bind_executor(ex, handler)), ...);
return result.get();
}
我们将编写一个演示协程,如下所示:
int main() {
static auto logger = [](auto name) {
return [name, start = now()](auto const&... args) {
((std::cout << name << "\t+" << (now() - start) / 1ms << "ms\t") << ... << args) << std::endl;
};
};
boost::asio::io_context ctx;
auto wg = make_work_guard(ctx);
spawn(ctx, [log = logger("coro1"),&wg](yield_context yield) {
log("started");
auto ex = get_associated_executor(yield);
timer a(ex, 100ms);
timer b(ex, 200ms);
timer c(ex, 300ms);
log("async_wait_any(a,b,c)");
async_wait_any(yield, a, b, c);
log("first completed");
async_wait_any(yield, c, b);
log("second completed");
assert(a.expiry() < now());
assert(b.expiry() < now());
// waiting again shows it expired as well
async_wait_any(yield, b);
// but c hasn't
assert(c.expiry() >= now());
// unless we wait for it
async_wait_any(yield, c);
log("third completed");
log("exiting");
wg.reset();
});
ctx.run();
}
这会打印出 Live On Coliru
coro1 +0ms started
coro1 +0ms async_wait_any(a,b,c)
coro1 +100ms first completed
coro1 +200ms second completed
coro1 +300ms third completed
coro1 +300ms exiting
注释、注意事项
棘手的问题:
很难决定将处理程序绑定到哪个执行程序,因为可能有多个关联的执行程序。但是,由于您使用的是协程,因此您将始终获得与 yield_context
关联的正确 strand_executor
在调用调用者的完成令牌之前进行取消很重要,否则协程在安全之前就已经恢复,导致潜在的生命周期问题
说到这里,既然现在我们post协程外异步操作,协程挂起,我们就需要一个work-guard,因为协程是不行的。
当 timer1
和 timer2
都完成时,下面的代码将打印到控制台。如何更改它以在 timer1
或 timer2
完成时打印, 然后取消另一个计时器。
#include <iostream>
#include <boost/asio.hpp>
#include <boost/asio/spawn.hpp>
int main() {
boost::asio::io_context io;
boost::asio::deadline_timer timer1(io, boost::posix_time::seconds(5));
boost::asio::deadline_timer timer2(io, boost::posix_time::seconds(1));
boost::asio::spawn(io, [&](boost::asio::yield_context yield){
timer1.async_wait(yield);
timer2.async_wait(yield);
std::cout << "Both timer1 and timer2 have finished" << std::endl;
});
io.run();
}
怎么样:
#include <iostream>
#include <boost/asio.hpp>
#include <boost/asio/spawn.hpp>
void print_timer_expired( bool& flag)
{
if( flag )
return;
flag = true;
std::cout << "Timer1 or timer2 has finished" << std::endl;
}
int main() {
boost::asio::io_context io;
bool flag = false; // true if message has been printed
boost::asio::deadline_timer timer1(io, boost::posix_time::seconds(5));
boost::asio::deadline_timer timer2(io, boost::posix_time::seconds(1));
boost::asio::spawn(io, [&](boost::asio::yield_context yield){
timer1.async_wait(yield);
print_timer_expired( flag );
});
boost::asio::spawn(io, [&](boost::asio::yield_context yield){
timer2.async_wait(yield);
print_timer_expired( flag );
});
io.run();
}
我把问题的意思理解为“你好async_wat_any(timer1, timer2, ..., yield)
。
另一个答案在指向回调完成处理程序以提供此功能时是正确的,但它们不提供回到单个协程的胶水。
现在 Asio's async operations 抽象出所有调用样式(回调、use_future、use_awaitable、yield_context 等...)之间的差异 - 将它们全部恢复本质上是在“回调”风格下。
因此,您可以创建自己的异步初始化,将它们联系在一起,草图:
template <typename Token>
auto async_wait_any( std::vector<std::reference_wrapper<timer>> timers, Token token) {
using Result =
boost::asio::async_result<std::decay_t<Token>, void(error_code)>;
using Handler = typename Result::completion_handler_type;
Handler handler(token);
Result result(handler);
for (timer& t : timers) {
t.async_wait([=](error_code ec) mutable {
if (ec == boost::asio::error::operation_aborted)
return;
for (timer& t : timers) {
t.cancel_one();
}
handler(ec);
});
}
return result.get();
}
现在在你的协程中你可以说:
timer a(ex, 100ms);
timer b(ex, 200ms);
timer c(ex, 300ms);
async_wait_any({a, b, c}, yield);
第一个完成后它将 return。
让我们演示一下
此外,使其更通用,而不是对计时器类型进行硬编码。事实上,在 Windows 环境中,您将能够等待可等待对象(如 Event, Mutex, Semaphore) with the same interface:
template <typename Token, typename... Waitable>
auto async_wait_any(Token&& token, Waitable&... waitable) {
using Result =
boost::asio::async_result<std::decay_t<Token>, void(error_code)>;
using Handler = typename Result::completion_handler_type;
Handler completion_handler(std::forward<Token>(token));
Result result(completion_handler);
// TODO use executors from any waitable?
auto ex = get_associated_executor(
completion_handler,
std::get<0>(std::tie(waitable...)).get_executor());
auto handler = [&, ex, ch = completion_handler](error_code ec) mutable {
if (ec != boost::asio::error::operation_aborted) {
(waitable.cancel_one(), ...);
post(ex, [=]() mutable { ch(ec); });
}
};
(waitable.async_wait(bind_executor(ex, handler)), ...);
return result.get();
}
我们将编写一个演示协程,如下所示:
int main() {
static auto logger = [](auto name) {
return [name, start = now()](auto const&... args) {
((std::cout << name << "\t+" << (now() - start) / 1ms << "ms\t") << ... << args) << std::endl;
};
};
boost::asio::io_context ctx;
auto wg = make_work_guard(ctx);
spawn(ctx, [log = logger("coro1"),&wg](yield_context yield) {
log("started");
auto ex = get_associated_executor(yield);
timer a(ex, 100ms);
timer b(ex, 200ms);
timer c(ex, 300ms);
log("async_wait_any(a,b,c)");
async_wait_any(yield, a, b, c);
log("first completed");
async_wait_any(yield, c, b);
log("second completed");
assert(a.expiry() < now());
assert(b.expiry() < now());
// waiting again shows it expired as well
async_wait_any(yield, b);
// but c hasn't
assert(c.expiry() >= now());
// unless we wait for it
async_wait_any(yield, c);
log("third completed");
log("exiting");
wg.reset();
});
ctx.run();
}
这会打印出 Live On Coliru
coro1 +0ms started
coro1 +0ms async_wait_any(a,b,c)
coro1 +100ms first completed
coro1 +200ms second completed
coro1 +300ms third completed
coro1 +300ms exiting
注释、注意事项
棘手的问题:
很难决定将处理程序绑定到哪个执行程序,因为可能有多个关联的执行程序。但是,由于您使用的是协程,因此您将始终获得与
关联的正确yield_context
strand_executor
在调用调用者的完成令牌之前进行取消很重要,否则协程在安全之前就已经恢复,导致潜在的生命周期问题
说到这里,既然现在我们post协程外异步操作,协程挂起,我们就需要一个work-guard,因为协程是不行的。