更改活动截止日期_计时器的到期时间未按预期工作

问题描述 投票:0回答:1

我尝试重新启动 boost asio 计时器,但它没有按预期工作

代码基于 www.boost.org/doc/libs/1_84_0/doc/html/boost_asio/reference/steady_timer.html#boost_asio.reference.steady_timer.change_an_active_waitable_timer_s_expiry_time

输出是

0 : Start at 
0 : ioContext.run()
1000 : on_timeout Timeout reached error=0
1100 : Too late, timer has already expired after 1100 ms
2200 : We managed to cancel the timer after 1100 ms
3300 : We managed to cancel the timer after 1100 ms
...

但应该是这样的:

0 : Start at 
0 : ioContext.run()
1000 : on_timeout Timeout reached error=0
1000 : Too late, timer has already expired after 1100 ms
2000 : on_timeout Timeout reached error=0
2000 : Too late, timer has already expired after 1100 ms
3000 : on_timeout Timeout reached error=0
3000 : Too late, timer has already expired after 1100 ms
...
#include <chrono>
#include <functional>
#include <iostream>
#include <thread>

#include <boost/asio.hpp>

long int millis() {
  static long int offset = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
  return std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count() - offset;
}

class BoostAsioTimerEval {
 public:
  BoostAsioTimerEval(boost::asio::io_context& ioContext)
      : ioContext_(ioContext), timer_(ioContext_), interval_(std::chrono::milliseconds{1000}), lastMillis_(millis()) {
    timer_.expires_after(interval_);
    timer_.async_wait(std::bind(&BoostAsioTimerEval::on_timeout, this, std::placeholders::_1));
    std::cout << millis() << " : Start at " << std::endl;
  }

  void on_event() {
    if (timer_.expires_after(interval_) > 0) {
      std::cout << millis() << " : We managed to cancel the timer after " << (millis() - lastMillis_) << " ms" << std::endl;
      timer_.async_wait(std::bind(&BoostAsioTimerEval::on_timeout, this, std::placeholders::_1));
    } else {
      std::cout << millis() << " : Too late, timer has already expired after " << (millis() - lastMillis_) << " ms" << std::endl;
      // kick again
      timer_.expires_after(interval_);
      timer_.async_wait(std::bind(&BoostAsioTimerEval::on_timeout, this, std::placeholders::_1));
    }
    lastMillis_ = millis();
  }

  void on_timeout(const boost::system::error_code& error) {
    if (error != boost::asio::error::operation_aborted) {
      std::cout << millis() << " : on_timeout Timeout reached error=" << error.value() << std::endl;
    } else {
      std::cout << millis() << " : on_timeout Timeout aborted error=" << error.value() << std::endl;
    }
  }

 private:
  boost::asio::io_context& ioContext_;
  boost::asio::steady_timer timer_;
  std::chrono::milliseconds interval_;
  long int lastMillis_;
};

int main(int argc, char** argv) {
  boost::asio::io_context ioContext;
  BoostAsioTimerEval boostAsioTimerEval(ioContext);
  std::thread worker([&]() {
    for (int i = 0; i < 10; ++i) {
      const int sleep = 1100;
      std::cout << millis() << " : sleep for " << sleep << " ms" << std::endl;
      std::this_thread::sleep_for(std::chrono::milliseconds(sleep));
      boostAsioTimerEval.on_event();
    }
    ioContext.stop();
  });
  std::cout << millis() << " : ioContext.run()" << std::endl;
  ioContext.run();
  worker.join();
  return 0;
}
c++ boost-asio
1个回答
0
投票

首先我清理了代码。所以

millis()
实际上可以直接读为
return (now() - offset) / 1ms
。¹

还要注意避免计时器更新时出现 TOCTOU 竞争条件,因此:

auto time  = now();
auto delta = (time - std::exchange(lastMillis_, time)) / 1ms;

我到达Live On Coliru

#include <boost/asio.hpp>
#include <iostream>
#include <syncstream>
namespace asio     = boost::asio;
using Clock        = std::chrono::steady_clock;
constexpr auto now = Clock::now;
using namespace std::chrono_literals;
using std::placeholders::_1;

static auto const g_start_time = now();

template <typename... Args> void trace(Args const&... args) {
    auto ts = (now() - g_start_time) / 1ms;
    ((std::osyncstream(std::cout) << "at" << std::setw(8) << ts << "ms: ") << ... << args) << std::endl;
}

struct TimerEval {
    TimerEval(asio::io_context& ioContext) : timer_(ioContext) {
        timer_.expires_after(interval_);
        timer_.async_wait(bind(&TimerEval::on_timeout, this, _1));
        trace("at ");
    }

    void on_event() {
        auto time  = now();
        auto delta = (time - std::exchange(lastMillis_, time)) / 1ms;

        if (timer_.expires_after(interval_) > 0) {
            trace("timer canceled after ", delta, " ms");
            timer_.async_wait(bind(&TimerEval::on_timeout, this, _1));
        } else {
            trace("timer already expired after ", delta, " ms");
            // kick again
            timer_.expires_after(interval_);
            timer_.async_wait(bind(&TimerEval::on_timeout, this, _1));
        }
    }

    void on_timeout(boost::system::error_code ec) { trace("on_timeout ", ec.message()); }

  private:
    asio::steady_timer timer_;
    Clock::duration    interval_   = 1000ms;
    Clock::time_point  lastMillis_ = now();
};

int main() {
    asio::io_context ioContext;
    TimerEval        timer(ioContext);

    std::thread worker([&]() {
        for (int i = 0; i < 10; ++i) {
            const auto delay = 1100ms;
            trace("sleep for ", delay);
            std::this_thread::sleep_for(delay);
            timer.on_event();
        }
        ioContext.stop();
    });

    trace("ioContext.run()");
    ioContext.run();
    worker.join();
}

确实打印:

at       0ms: at 
at       0ms: ioContext.run()
at       0ms: sleep for 1100ms
at    1000ms: on_timeout Success
at    1100ms: timer already expired after 1100 ms
at    1100ms: sleep for 1100ms
at    2200ms: timer canceled after 1100 ms
at    2200ms: sleep for 1100ms
at    3301ms: timer canceled after 1100 ms
at    3301ms: sleep for 1100ms
at    4401ms: timer canceled after 1100 ms
at    4401ms: sleep for 1100ms
at    5501ms: timer canceled after 1100 ms
at    5501ms: sleep for 1100ms
at    6601ms: timer canceled after 1100 ms
at    6601ms: sleep for 1100ms
at    7701ms: timer canceled after 1100 ms
at    7702ms: sleep for 1100ms
at    8802ms: timer canceled after 1100 ms
at    8802ms: sleep for 1100ms
at    9902ms: timer canceled after 1100 ms
at    9902ms: sleep for 1100ms
at   11002ms: timer canceled after 1100 ms

我必须承认我从未使用过

expires_after
的返回值。相反,我想观察到,没有任何
on_timeout
调用 actually 显示
asio::error::operation_aborted
(“操作已取消”)作为 ec。所以还有其他问题!

无论如何,您评论的行

// kick again
确实没有任何意义,因为在just完成之后,意识到没有任何内容被取消,它再次设置了到期日。

让我们将

on_event
简化为:

void on_event() {
    auto time  = now();
    auto delta = (time - std::exchange(lastMillis_, time)) / 1ms;

    auto n = timer_.expires_after(interval_);
    timer_.async_wait(bind(&TimerEval::on_timeout, this, _1));
    trace("timer reset (n = ", n, ") after ", delta, " ms");
}

未定义的行为

我看到的第一个真正的问题是你有 UB(你在没有同步的情况下从多个线程访问

timer
。现在,当你解决这个问题时,你会注意到一些有趣的事情:

    // oops: timer.on_event(); FIXED:
    post(ioc, [&timer] { timer.on_event(); });

真正的问题

运行即可打印

at       0ms: start
at       1ms: ioContext.run()
at       1ms: sleep for 1100ms
at    1000ms: on_timeout Success after 1000
at    1102ms: sleep for 1100ms
at    2202ms: sleep for 1100ms
at    3302ms: sleep for 1100ms
at    4402ms: sleep for 1100ms

原因是,在第一个计时器完成后(

on_timeout
),服务将无法工作,因为您的线程不工作。事实上,如果你添加一点痕迹:

trace("ioContext.run()");
ioc.run();
trace("ioContext.run() returned");

你得到了

at       0ms: start
at       1ms: ioContext.run()
at       1ms: sleep for 1100ms
at    1000ms: on_timeout Success after 1000
at    1000ms: ioContext.run() returned

那是你的问题。

工作卫士

有多种方法可以解决此问题,但让我们首先选择最“低级”的方法:

std::thread worker([&, work = make_work_guard(ioc)]() {
    for (int i = 0; i < 10; ++i) {
        const auto delay = 1100ms;
        trace("sleep for ", delay);
        std::this_thread::sleep_for(delay);
        post(ioc, [&timer] { timer.on_event(); });
    }
    ioc.stop();
});

work
守卫使服务至少保持到工作线程结束为止。事实上,您可以删除
ioc.stop()
,因为它是多余的!

住在Coliru

#include <boost/asio.hpp>
#include <iostream>
#include <syncstream>
namespace asio     = boost::asio;
using Clock        = std::chrono::steady_clock;
constexpr auto now = Clock::now;
using namespace std::chrono_literals;
using std::placeholders::_1;

static auto const g_start_time = now();

template <typename... Args> void trace(Args const&... args) {
    auto ts = (now() - g_start_time) / 1ms;
    ((std::osyncstream(std::cout) << "at" << std::setw(8) << ts << "ms: ") << ... << args) << std::endl;
}

struct TimerEval {
    TimerEval(asio::io_context& ioc) : timer_(ioc) {
        timer_.expires_after(interval_);
        timer_.async_wait(bind(&TimerEval::on_timeout, this, _1));
        trace("start");
    }

    void on_event() {
        auto time  = now();
        auto delta = (time - std::exchange(lastMillis_, time)) / 1ms;

        auto n = timer_.expires_after(interval_);
        timer_.async_wait(bind(&TimerEval::on_timeout, this, _1));
        trace("timer reset (n = ", n, ") after ", delta, " ms");
    }

    void on_timeout(boost::system::error_code ec) {
        trace("on_timeout ", ec.message(), " after ", (now() - lastMillis_) / 1ms);
    }

  private:
    asio::steady_timer timer_;
    Clock::duration const interval_   = 1000ms;
    Clock::time_point     lastMillis_ = now();
};

int main() {
    asio::io_context ioc;
    TimerEval        timer(ioc);

    std::thread worker([&, work = make_work_guard(ioc)]() {
        for (int i = 0; i < 10; ++i) {
            const auto delay = 1100ms;
            trace("sleep for ", delay);
            std::this_thread::sleep_for(delay);
            post(ioc, [&timer] { timer.on_event(); });
        }
    });

    trace("ioContext.run()");
    ioc.run();
    trace("ioContext.run() returned");
    worker.join();
}

打印

at       0ms: start
at       0ms: ioContext.run()
at       0ms: sleep for 1100ms
at    1000ms: on_timeout Success after 1000
at    1100ms: sleep for 1100ms
at    1100ms: timer reset (n = 0) after 1100 ms
at    2100ms: on_timeout Success after 1000
at    2200ms: sleep for 1100ms
at    2200ms: timer reset (n = 0) after 1100 ms
at    3200ms: on_timeout Success after 1000
at    3300ms: sleep for 1100ms
at    3300ms: timer reset (n = 0) after 1100 ms
at    4301ms: on_timeout Success after 1000
at    4401ms: sleep for 1100ms
at    4401ms: timer reset (n = 0) after 1100 ms
at    5401ms: on_timeout Success after 1000
at    5501ms: sleep for 1100ms
at    5501ms: timer reset (n = 0) after 1100 ms
at    6501ms: on_timeout Success after 1000
at    6601ms: sleep for 1100ms
at    6601ms: timer reset (n = 0) after 1100 ms
at    7601ms: on_timeout Success after 1000
at    7701ms: sleep for 1100ms
at    7701ms: timer reset (n = 0) after 1100 ms
at    8701ms: on_timeout Success after 1000
at    8802ms: sleep for 1100ms
at    8802ms: timer reset (n = 0) after 1100 ms
at    9802ms: on_timeout Success after 1000
at    9902ms: sleep for 1100ms
at    9902ms: timer reset (n = 0) after 1100 ms
at   10902ms: on_timeout Success after 1000
at   11002ms: timer reset (n = 0) after 1100 ms
at   12002ms: on_timeout Success after 1000
at   12002ms: ioContext.run() returned

事实上,减少延迟(例如 900 毫秒)现在表明从

n
返回的
expires_at
实际上 准确的。这只是服务没有运行的事实。


1 我不知道为什么人们不断地用那些令人发指的可笑咒语来伤害自己,比如std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count()

 - 只是
现在怎么办!?!

© www.soinside.com 2019 - 2024. All rights reserved.