Я использую несколько 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
Согласно ссылка на deadline_timer :: отменить:
Если время таймера уже истекло при вызове cancel (), то обработчики для асинхронных операций ожидания будут:
уже были вызваны; или же
были поставлены в очередь для вызова в ближайшее время.
Эти обработчики больше не могут быть отменены, и поэтому передается код ошибки, который указывает на успешное завершение операции ожидания.
Мы можем знать, что призвание cancel()
не может отменить таймер, который уже был в очереди на стрельбу.
И похоже, что dealine_timer не перекрывает деструктор. (В списке участников deadline_timer нет деструктора)
В вашем фрагменте кода все таймеры сработают практически одновременно. Что касается того, что asio будет использовать некоторые внутренние потоки, то вполне вероятно, что когда вызывается один обработчик завершения, остальные ставятся в очередь.
Других решений пока нет …