【问题标题】:How to wait for either of two timers to finish (Boost Asio)如何等待两个计时器中的任何一个完成(Boost Asio)
【发布时间】:2021-06-29 04:53:39
【问题描述】:

timer1timer2 都完成 时,下面的代码将打印到控制台。当任一 timer1timer2 完成时,如何将其更改为打印,然后取消另一个计时器。

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

}

【问题讨论】:

    标签: c++ c++11 boost boost-asio boost-coroutine


    【解决方案1】:

    我认为这个问题的意思是“你好吗async_wat_any(timer1, timer2, ..., yield)

    另一个答案是正确的指向回调完成处理程序来提供这个,但他们没有提供胶水回到单个协程。

    现在Asio's async operations 抽象出所有调用样式(callback、use_future、use_awaitable、yield_context 等)之间的差异——本质上将它们全部归为“callback”样式。

    因此,您可以制作自己的异步启动,将这些粗略的草图联系在一起:

    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 环境中,您将能够使用 same interface 等待可等待对象(如 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
    

    注意事项、注意事项

    小窍门:

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

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

    • 说到这里,既然现在我们在协程之外发布异步操作,协程挂起,我们需要一个工作守卫,因为协程不工作。

    【讨论】:

    • 非常感谢@sehe。是的,我要求实现async_wait_any。这是一个非常有用的答案。
    【解决方案2】:

    怎么样:

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

    【讨论】:

    • 谢谢@ravenspoint。抱歉,我应该更具体一些,但我不希望“Timer1 或 timer2 已完成”打印两次。即,一旦其中一个计时器完成,我想取消(或只是忽略)另一个。这可能吗?
    • 我真的很困惑为什么这个答案不仅仅是this?换句话说,感觉它基本上是在说“不要使用协程”,或者至少没有回答你如何async_wait_any(timer1, timer2, yield) 的问题,这就是我阅读问题的方式
    • 添加了一个answer of my own,它显示了我期望看到的内容。看到它Live on Wandbox
    • @sehe 我将问题读作“如何更改它(我的代码)以在 timer1 或 timer2 完成时打印”也就是说,在不引入大量集合的情况下实现该功能的最小修改钟声和口哨声。
    • 很公平。从这个意义上说,this 更好。我认为它根本不再是一个功能,因为你拆分了 coro。控制流是整个目标。是的,你可以从狭义上阅读散文描述,但那是……只见树木不见森林。
    猜你喜欢
    • 2020-08-03
    • 2020-05-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多