3

boost::asio::deadline_timer在一个 io_service 对象上使用多个。std::shared_ptrboost::asio::deadline_timer存储在std::map<int, std::shared_ptr<debug_tim>> timers带有索引的容器中。

在计时器处理程序中,我擦除了其他boost::asio::deadline_timer. 但是,似乎已擦除的计时器woule 经常被成功错误代码触发。

有什么办法可以避免。我希望对应于已擦除的计时器处理程序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 解决方案效果很好。

请参阅 如何避免触发已经破坏的 boost::asio::deadline_timer

4

1 回答 1

1

根据deadline_timer::cancel的参考

如果在调用 cancel() 时计时器已经过期,那么异步等待操作的处理程序将:

  • 已经被调用;或者

  • 已在不久的将来排队等待调用。

这些处理程序不能再被取消,因此会传递一个错误代码,指示等待操作成功完成。

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

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

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

于 2017-03-27T11:41:24.923 回答