Как избежать стрельбы по уже уничтоженному бусту :: asio :: deadline_timer

Я использую несколько boost::asio::deadline_timer на одном объекте io_service. 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 ?

редактировать

Мое слабое решение работает хорошо.

Увидеть
Как избежать стрельбы по уже уничтоженному бусту :: asio :: deadline_timer

1

Решение

Согласно ссылка на deadline_timer :: отменить:

Если время таймера уже истекло при вызове cancel (), то обработчики для асинхронных операций ожидания будут:

  • уже были вызваны; или же

  • были поставлены в очередь для вызова в ближайшее время.

Эти обработчики больше не могут быть отменены, и поэтому передается код ошибки, который указывает на успешное завершение операции ожидания.

Мы можем знать, что призвание cancel() не может отменить таймер, который уже был в очереди на стрельбу.

И похоже, что dealine_timer не перекрывает деструктор. (В списке участников deadline_timer нет деструктора)

В вашем фрагменте кода все таймеры сработают практически одновременно. Что касается того, что asio будет использовать некоторые внутренние потоки, то вполне вероятно, что когда вызывается один обработчик завершения, остальные ставятся в очередь.

1

Другие решения

Других решений пока нет …

По вопросам рекламы [email protected]