如何避免射击已经被摧毁boost::asio::deadline_timer

How to avoid firing already destroyed boost::asio::deadline_timer

我在一个 io_service 对象上使用多个 boost::asio::deadline_timerboost::asio::deadline_timer 中的 std::shared_ptr 存储在索引为 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 解决方案效果很好。

根据 reference of deadline_timer::cancel:

If the timer has already expired when cancel() is called, then the handlers for asynchronous wait operations will:

  • have already been invoked; or

  • have been queued for invocation in the near future.

These handlers can no longer be cancelled, and therefore are passed an error code that indicates the successful completion of the wait operation.

我们可以知道调用cancel()并不能取消已经排队等待触发的定时器

而且 dealine_timer 似乎没有覆盖析构函数。 (deadline_timer的成员列表中没有析构函数)

在您的代码片段中,所有计时器几乎同时触发。关于 asio 将使用一些内部线程,很可能当一个完成处理程序被调用时,其他的正在排队。