我正在尝试安全取消boost::asio::basic_waitable_timer<std::chrono::steady_clock>
。
根据这个answer,这段代码可以做到这一点:
timer.get_io_service().post([&]{timer.cancel();})
我担心这对我不起作用。
我做错了吗?
这是我的代码:
#include <iostream>
#include "boost/asio.hpp"
#include <chrono>
#include <thread>
#include <random>
boost::asio::io_service io_service;
boost::asio::basic_waitable_timer<std::chrono::steady_clock> timer(io_service);
std::atomic<bool> started;
void handle_timeout(const boost::system::error_code& ec)
{
if (!ec) {
started = true;
std::cerr << "tid: " << std::this_thread::get_id() << ", handle_timeout\n";
timer.expires_from_now(std::chrono::milliseconds(10));
timer.async_wait(&handle_timeout);
} else if (ec == boost::asio::error::operation_aborted) {
std::cerr << "tid: " << std::this_thread::get_id() << ", handle_timeout aborted\n";
} else {
std::cerr << "tid: " << std::this_thread::get_id() << ", handle_timeout another error\n";
}
}
int main() {
std::cout << "tid: " << std::this_thread::get_id() << ", Hello, World!" << std::endl;
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<> dis(1, 100);
for (auto i = 0; i < 1000; i++) {
started = false;
std::thread t([&](){
timer.expires_from_now(std::chrono::milliseconds(0));
timer.async_wait(&handle_timeout);
io_service.run();
});
while (!started) {};
auto sleep = dis(gen);
std::cout << "tid: " << std::this_thread::get_id() << ", i: " << i << ", sleeps for " << sleep << " [ms]" << std::endl;
std::this_thread::sleep_for(std::chrono::milliseconds(sleep));
timer.get_io_service().post([](){
std::cerr << "tid: " << std::this_thread::get_id() << ", cancelling in post\n";
timer.cancel();
});
// timer.cancel();
std::cout << "tid: " << std::this_thread::get_id() << ", i: " << i << ", waiting for thread to join()" << std::endl;
t.join();
io_service.reset();
}
return 0;
}
这是输出:
...
tid:140737335076608,handle_timeout
tid:140737335076608,handle_timeout
tid:140737353967488,i:2,等待线程加入()
tid:140737335076608,在帖子中取消
tid:140737335076608,handle_timeout aborted
tid:140737353967488,i:3,睡眠时间为21 [ms]
tid:140737335076608,handle_timeout
tid:140737353967488,i:3,等待线程加入()
tid:140737335076608,handle_timeout
tid:140737335076608,在帖子中取消
tid:140737335076608,handle_timeout
tid:140737335076608,handle_timeout
tid:140737335076608,handle_timeout
tid:140737335076608,handle_timeout
tid:140737335076608,handle_timeout
...
永远地继续......
如您所见,正在从相应的线程调用timer.cancel()
:
tid:140737335076608,在帖子中取消
但没有
tid:140737335076608,handle_timeout已中止
之后。
Main等待永远。
答案 0 :(得分:11)
取消 是安全的。
它不健壮。当计时器未挂起时,您没有考虑这种情况。然后,取消它一次,但是一旦调用完成处理程序,它就会启动一个新的异步等待。
以下是我如何跟踪问题的详细步骤。
摘要 TL; DR
取消时间只会取消飞行中的异步操作。
如果要关闭异步调用链,则必须使用其他逻辑。下面给出一个例子。
启用
#define BOOST_ASIO_ENABLE_HANDLER_TRACKING 1
这会生成可以使用boost/libs/asio/tools/handlerviz.pl
显示的输出:
如您所见,async_wait
在取消时正在进行中。
(截断,因为它会无限运行)
请注意完成处理程序如何看到cc=system:0
,而不是cc=system:125
(对于operation_aborted
)。这是发布的取消实际上没有“采取”这一事实的症状。唯一的逻辑解释(在图中不可见)是在调用cancel之前计时器已经过期。
让我们比较原始痕迹¹
¹消除了嘈杂的差异
所以,我们有一个领先优势。我们能检测出来吗?
timer.get_io_service().post([](){
std::cerr << "tid: " << std::this_thread::get_id() << ", cancelling in post\n";
if (timer.expires_from_now() >= std::chrono::steady_clock::duration(0)) {
timer.cancel();
} else {
std::cout << "PANIC\n";
timer.cancel();
}
});
打印:
tid: 140113177143232, i: 0, waiting for thread to join()
tid: 140113177143232, i: 1, waiting for thread to join()
tid: 140113177143232, i: 2, waiting for thread to join()
tid: 140113177143232, i: 3, waiting for thread to join()
tid: 140113177143232, i: 4, waiting for thread to join()
tid: 140113177143232, i: 5, waiting for thread to join()
tid: 140113177143232, i: 6, waiting for thread to join()
tid: 140113177143232, i: 7, waiting for thread to join()
tid: 140113177143232, i: 8, waiting for thread to join()
tid: 140113177143232, i: 9, waiting for thread to join()
tid: 140113177143232, i: 10, waiting for thread to join()
tid: 140113177143232, i: 11, waiting for thread to join()
tid: 140113177143232, i: 12, waiting for thread to join()
tid: 140113177143232, i: 13, waiting for thread to join()
tid: 140113177143232, i: 14, waiting for thread to join()
tid: 140113177143232, i: 15, waiting for thread to join()
tid: 140113177143232, i: 16, waiting for thread to join()
tid: 140113177143232, i: 17, waiting for thread to join()
tid: 140113177143232, i: 18, waiting for thread to join()
tid: 140113177143232, i: 19, waiting for thread to join()
tid: 140113177143232, i: 20, waiting for thread to join()
tid: 140113177143232, i: 21, waiting for thread to join()
tid: 140113177143232, i: 22, waiting for thread to join()
tid: 140113177143232, i: 23, waiting for thread to join()
tid: 140113177143232, i: 24, waiting for thread to join()
tid: 140113177143232, i: 25, waiting for thread to join()
tid: 140113177143232, i: 26, waiting for thread to join()
PANIC
我们能否以另一种更清晰的方式传达“超级取消”?我们只有timer
对象可以使用,当然:
timer
对象没有很多属性可供使用。没有close()
或类似的,就像套接字一样,可用于将计时器置于某种无效状态。
但是,有到期时间点,我们可以使用特殊域 值为我们的应用程序发出“无效”信号:
timer.get_io_service().post([](){
std::cerr << "tid: " << std::this_thread::get_id() << ", cancelling in post\n";
// also cancels:
timer.expires_at(Timer::clock_type::time_point::min());
});
这个“特殊值”在完成处理程序中很容易处理:
void handle_timeout(const boost::system::error_code& ec)
{
if (!ec) {
started = true;
if (timer.expires_at() != Timer::time_point::min()) {
timer.expires_from_now(std::chrono::milliseconds(10));
timer.async_wait(&handle_timeout);
} else {
std::cerr << "handle_timeout: detected shutdown\n";
}
}
else if (ec != boost::asio::error::operation_aborted) {
std::cerr << "tid: " << std::this_thread::get_id() << ", handle_timeout error " << ec.message() << "\n";
}
}