1

两者 都完成时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();

}
4

2 回答 2

1

怎么样:

#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();

}
于 2021-04-02T17:31:18.333 回答
1

我把这个问题理解为“你好吗async_wat_any(timer1, timer2, ..., yield)

另一个答案是正确地指向回调完成处理程序以提供此功能,但它们没有将胶水提供回单个协程。

现在, Asio 的异步操作抽象出了所有调用样式(回调、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);

当第一个完成时它将返回。

让我们演示

此外,使其更通用,而不是对计时器类型进行硬编码。事实上,在 Windows 环境中,您将能够等待具有相同接口的可等待对象(如Event、Mutex、Semaphore):

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

注释、注意事项

棘手的位:

  • 很难决定将处理程序绑定到哪个执行程序,因为可能有多个关联的执行程序。但是,由于您使用的是协程,因此您将始终获得strand_executoryield_context

  • 在调用调用者的完成令牌之前进行取消很重要,因为否则协程在安全之前已经恢复,导致潜在的生命周期问题

  • 说到这一点,既然我们现在在协程之外发布异步操作并且协程暂停,我们将需要一个工作守卫,因为协程不工作。

于 2021-04-03T04:18:42.563 回答