我尝试重新启动 boost asio 计时器,但它没有按预期工作
输出是
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;
}
首先我清理了代码。所以
millis()
实际上可以直接读为 return (now() - offset) / 1ms
。¹
还要注意避免计时器更新时出现 TOCTOU 竞争条件,因此:
auto time = now();
auto delta = (time - std::exchange(lastMillis_, time)) / 1ms;
#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()
,因为它是多余的!
#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()
- 只是现在怎么办!?!