如何避免触发已经破坏的 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_timerstd::shared_ptrboost::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

如何在 Boost.ASIO 中分配已连接的本机套接字类型 (TCP)

使用boost asio的线程池

Boost asio基本概念

boost asio 异步等待条件变量

C++ boost::asio https 通过代理