如何避免触发已经破坏的 boost::asio::deadline_timer
Posted
技术标签:
【中文标题】如何避免触发已经破坏的 boost::asio::deadline_timer【英文标题】:How to avoid firing already destroyed boost::asio::deadline_timer 【发布时间】:2017-03-27 11:29:17 【问题描述】:我在一个 io_service 对象上使用了多个 boost::asio::deadline_timer
。 std::shared_ptr
的 boost::asio::deadline_timer
存储在带有索引的容器 std::map<int, std::shared_ptr<debug_tim>> timers
中。
在计时器处理程序中,我删除了其他boost::asio::deadline_timer
。但是,似乎已擦除的计时器会经常以成功错误代码触发。
有什么办法可以避免。我希望与已删除的boost::asio::deadline_timer
对应的计时器处理程序始终以Operation canceled
触发。
我错过了什么吗?
这是重现行为的代码
https://wandbox.org/permlink/G0qzYcqauxdqw4i7
#include <iostream>
#include <memory>
#include <boost/asio.hpp>
// deadline_timer with index ctor/dtor print
struct debug_tim : boost::asio::deadline_timer
debug_tim(boost::asio::io_service& ios, int i) : boost::asio::deadline_timer(ios), i(i)
std::cout << "debug_tim() " << i << std::endl;
~debug_tim()
std::cout << "~debug_tim() " << i << std::endl;
int i;
;
int main()
boost::asio::io_service ios;
std::map<int, std::shared_ptr<debug_tim>> timers;
for (int i = 0; i != 5; ++i)
auto tim = std::make_shared<debug_tim>(ios, i);
std::cout << "set timer " << i << std::endl;
tim->expires_from_now(boost::posix_time::seconds(1));
timers.emplace(i, tim);
tim->async_wait([&timers, i](auto ec)
std::cout << "timer fired " << i << " : " << ec.message() << std::endl;
auto it = timers.find(i);
if (it == timers.end())
std::cout << " already destructed." << std::endl;
else
int other_idx = i + 1; // erase other timer (e.g. i + 1)
timers.erase(other_idx);
std::cout << " erased " << other_idx << std::endl;
);
ios.run();
在清除计时器之前,我还会致电boost::asio::deadline_timer::cancel()
。但是,我得到了类似的结果。这是取消版本:
https://wandbox.org/permlink/uM0yMFufkyn9ipdG
#include <iostream>
#include <memory>
#include <boost/asio.hpp>
// deadline_timer with index ctor/dtor print
struct debug_tim : boost::asio::deadline_timer
debug_tim(boost::asio::io_service& ios, int i) : boost::asio::deadline_timer(ios), i(i)
std::cout << "debug_tim() " << i << std::endl;
~debug_tim()
std::cout << "~debug_tim() " << i << std::endl;
int i;
;
int main()
boost::asio::io_service ios;
std::map<int, std::shared_ptr<debug_tim>> timers;
for (int i = 0; i != 5; ++i)
auto tim = std::make_shared<debug_tim>(ios, i);
std::cout << "set timer " << i << std::endl;
tim->expires_from_now(boost::posix_time::seconds(1));
timers.emplace(i, tim);
tim->async_wait([&timers, i](auto ec)
std::cout << "timer fired " << i << " : " << ec.message() << std::endl;
auto it = timers.find(i);
if (it == timers.end())
std::cout << " already destructed." << std::endl;
else
int other_idx = i + 1; // erase other timer (e.g. i + 1)
auto other_it = timers.find(other_idx);
if (other_it != timers.end())
other_it->second->cancel();
timers.erase(other_it);
std::cout << " erased " << other_idx << std::endl;
);
ios.run();
编辑
菲利克斯,谢谢你的回答。我了解boost::asio::deadline::timer::cancel()
的行为。我总是需要关心boost::asio::deadline::timer
的生命周期。在我的项目的实际代码中,`boost::asio::deadline::timer` 是另一个对象(例如会话对象)的成员变量。在计时器处理程序中,它访问对象。很危险。
我考虑如何编写安全代码。我想出使用std::weak_ptr
来检查对象的生命周期。
这是更新后的代码:
#include <iostream>
#include <memory>
#include <boost/asio.hpp>
// deadline_timer with index ctor/dtor print
struct debug_tim : boost::asio::deadline_timer
debug_tim(boost::asio::io_service& ios, int i) : boost::asio::deadline_timer(ios), i(i)
std::cout << "debug_tim() " << i << std::endl;
~debug_tim()
std::cout << "~debug_tim() " << i << std::endl;
int i;
;
int main()
boost::asio::io_service ios;
std::map<int, std::shared_ptr<debug_tim>> timers;
for (int i = 0; i != 5; ++i)
auto tim = std::make_shared<debug_tim>(ios, i);
std::cout << "set timer " << i << std::endl;
tim->expires_from_now(boost::posix_time::seconds(1));
timers.emplace(i, tim);
// Capture tim as the weak_ptr wp
tim->async_wait([&timers, i, wp = std::weak_ptr<debug_tim>(tim)](auto ec)
std::cout << "timer fired " << i << " : " << ec.message() << std::endl;
// Check the lifetime of wp
if (!wp.lock()) std::cout << " timer freed." << std::endl; // return here on actual code
auto it = timers.find(i);
if (it == timers.end())
std::cout << " already destructed." << std::endl;
else
int other_idx = i + 1; // erase other timer (e.g. i + 1)
timers.erase(other_idx);
std::cout << " erased " << other_idx << std::endl;
);
ios.run();
这是避免访问具有 boost::asio::deadline_timer
的已删除对象的好方法吗?
编辑
我的weak_ptr 解决方案效果很好。
看 How to avoid firing already destroyed boost::asio::deadline_timer
【问题讨论】:
我花了三天时间才弄清楚计时器的行为。您的解决方案是否满足I expect that the timer handler that corresponding to the erased boost::asio::deadline_timer always fires with Operation canceled
,这正是我正在寻找的。span>
【参考方案1】:
根据reference of deadline_timer::cancel:
如果在调用 cancel() 时计时器已经过期,那么异步等待操作的处理程序将:
已经被调用;或
已在不久的将来排队等待调用。
这些处理程序不能再被取消,因此会传递一个错误代码,指示等待操作成功完成。
我们可以知道,调用cancel()
不能取消已经排队等待触发的定时器。
dealine_timer 似乎没有覆盖析构函数。 (deadline_timer的成员列表中没有析构函数)
在您的代码 sn-p 中,所有计时器将几乎同时触发。关于 asio 将使用一些内部线程,很可能当调用一个完成处理程序时,其他处理程序正在排队。
【讨论】:
感谢您的回答。我了解 dead_time_timer 的行为。我想避免或想知道取消状态的原因是我访问了超时处理程序中具有deadline_timer 的对象。现在,我的目标已更改为如何避免在计时器处理程序中访问已删除的资源。所以我更新(添加)了我的问题。你能检查一下我的方法吗? @TakatoshiKondo 是的,这很好。同时触发它们时要注意竞争条件。以上是关于如何避免触发已经破坏的 boost::asio::deadline_timer的主要内容,如果未能解决你的问题,请参考以下文章
Boost::Asio : io_service.run() vs poll() 或者我如何在主循环中集成 boost::asio