我在一段现实代码中遇到问题,boost::asio::deadline_timer
调用属于已删除类的函数,偶尔会导致分段错误。
我遇到的问题是删除deadline_timer是从同一个io_service上的另一个计时器运行的。删除第一个deadline_timer
将触发对要运行的函数的最后一次调用,并出现boost::asio::error::operation_aborted
错误。但是,这只能在删除完成后在(相同的)io_service
上进行调度,但到那时该对象已被删除,因此不再有效。
所以我的问题是:如何防止这种情况发生?
以下是具有相同错误的简化示例:
//============================================================================
// Name : aTimeToKill.cpp
// Author : Pelle
// Description : Delete an object using a timer, from a timer
//============================================================================
#include <iostream>
#include <boost/function.hpp>
#include <boost/bind.hpp>
#include <boost/asio.hpp>
#include <boost/thread.hpp>
using namespace std;
using namespace boost;
struct TimeBomb
{
bool m_active;
asio::deadline_timer m_runTimer;
TimeBomb(boost::asio::io_service& ioService)
: m_active(true)
, m_runTimer(ioService)
{
cout << "Bomb placed @"<< hex << (int)this << endl;
m_runTimer.expires_from_now(boost::posix_time::millisec(1000));
m_runTimer.async_wait(boost::bind(&TimeBomb::executeStepFunction, this, _1));
}
~TimeBomb()
{
m_active = false;
m_runTimer.cancel();
cout << "Bomb defused @"<< hex << (int)this << endl;
}
void executeStepFunction(const boost::system::error_code& error)
{
// Canceled timer
if (error == boost::asio::error::operation_aborted)
{
std::cout << "Timer aborted: " << error.message() << " @" << std::hex << (int)this << std::endl;
return;
}
if (m_active)
{
// Schedule next step
cout << "tick .." <<endl;
m_runTimer.expires_from_now(
boost::posix_time::millisec(1000));
m_runTimer.async_wait(boost::bind(&TimeBomb::executeStepFunction, this, _1));
}
}
};
struct BomberMan
{
asio::deadline_timer m_selfDestructTimer;
TimeBomb* myBomb;
BomberMan(boost::asio::io_service& ioService)
: m_selfDestructTimer(ioService)
{
cout << "BomberMan ready " << endl;
myBomb = new TimeBomb(ioService);
m_selfDestructTimer.expires_from_now(boost::posix_time::millisec(10500));
m_selfDestructTimer.async_wait(boost::bind(&BomberMan::defuseBomb, this, _1));
}
void defuseBomb(const boost::system::error_code& error)
{
cout << "Defusing TimeBomb" << endl;
delete myBomb;
}
};
int main()
{
boost::asio::io_service m_ioService;
BomberMan* b = new BomberMan(m_ioService);
m_ioService.run();
return 0;
}
./aTimeToKill
BomberMan ready
Bomb placed @9c27198
tick ..
tick ..
tick ..
tick ..
tick ..
tick ..
tick ..
tick ..
tick ..
tick ..
Defusing TimeBomb
Bomb defused @9c27198
Timer aborted: Operation canceled @9c27198
删除后会打印最后一行,说明我的问题。
答案 0 :(得分:6)
解决此问题的典型方法是使用shared_ptr
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/enable_shared_from_this.hpp>
#include <boost/shared_ptr.hpp>
#include <iostream>
using namespace std;
struct TimeBomb : public boost::enable_shared_from_this<TimeBomb>
{
bool m_active;
boost::asio::deadline_timer m_runTimer;
TimeBomb(boost::asio::io_service& ioService)
: m_active(true)
, m_runTimer(ioService)
{
cout << "Bomb placed @"<< hex << this << endl;
m_runTimer.expires_from_now(boost::posix_time::millisec(1000));
}
void start()
{
m_runTimer.async_wait(boost::bind(&TimeBomb::executeStepFunction, shared_from_this(), _1));
}
void stop()
{
m_runTimer.cancel();
}
~TimeBomb()
{
m_active = false;
m_runTimer.cancel();
cout << "Bomb defused @"<< hex << this << endl;
}
void executeStepFunction(const boost::system::error_code& error)
{
// Canceled timer
if (error == boost::asio::error::operation_aborted)
{
std::cout << "Timer aborted: " << error.message() << " @" << std::hex << this << std::endl;
return;
}
if (m_active)
{
// Schedule next step
cout << "tick .." <<endl;
m_runTimer.expires_from_now(
boost::posix_time::millisec(1000));
m_runTimer.async_wait(boost::bind(&TimeBomb::executeStepFunction, shared_from_this(), _1));
}
}
};
struct BomberMan
{
boost::asio::deadline_timer m_selfDestructTimer;
boost::shared_ptr<TimeBomb> myBomb;
BomberMan(boost::asio::io_service& ioService)
: m_selfDestructTimer(ioService)
{
cout << "BomberMan ready " << endl;
myBomb.reset( new TimeBomb(ioService) );
myBomb->start();
m_selfDestructTimer.expires_from_now(boost::posix_time::millisec(10500));
m_selfDestructTimer.async_wait(boost::bind(&BomberMan::defuseBomb, this, _1));
}
void defuseBomb(const boost::system::error_code& error)
{
cout << "Defusing TimeBomb" << endl;
myBomb->stop();
}
};
int main()
{
boost::asio::io_service m_ioService;
BomberMan* b = new BomberMan(m_ioService);
m_ioService.run();
return 0;
}
答案 1 :(得分:2)
这就是boost::shared_ptr
和boost::enable_shared_from_this
的原因。从TimeBomb
继承boost::enable_shared_from_this
类,如下所示:
struct TimeBomb : public boost::enable_shared_from_this< TimeBomb >
{
...
}
实例化共享ptr而不是裸ptr:
boost::shared_ptr< TimeBomb > myBomb;
...
myBomb.reset( new TimeBomb(ioService) );
最后在TimeBomb
使用shared_from_this()
而不是this
来构建处理程序。
m_runTimer.async_wait( boost::bind( &TimeBomb::executeStepFunction, shared_from_this(), _1));
当然TimeBomb
类应该公开cancel
方法,通过该方法取消异步操作,而不是删除,或者在这种情况下,重置shared_ptr。
答案 2 :(得分:0)
来自Sam Miller的shared_ptr答案之所以有效,是因为使用shared_ptr可以让TimeBomb在BomberMan的生命周期内保持不变。这对你来说可能没问题,也可能没有。
建议更完整的解决方案是从工厂获取TimeBomb实例,然后在完成时将它们释放回来,而不是新建和删除它们(将它们作为标准指针,而不是shared_ptrs,因为你没有&# 39;即使你正在控制生命周期,也要拥有它们。工厂可以让它们一直闲置直到它们被取消,然后为你删除它们。保持Sam Miller的stop()功能不变。
要实现此目的,请从
行的接口派生工厂class ITimeBombObserver
{
public:
virtual void AllOperationsComplete(TimeBomb& TmBmb)=0;
};
将工厂作为ITimeBombObserver传递给每个TimeBomb,并取消TimeBomb调用此函数。工厂可以清理&#34;使用&#34;每次创建或发布一个TimeBombs,或者使用计划清理或其他一些方法,最适合您的应用程序。
使用这种方法你的BomberMan甚至不需要在defuseBomb()中显式释放TimeBomb,如果它不想要,对stop()的调用可以自动释放(尽管在这种情况下你仍然应该将指针归零,因为此时它变得无效。)这是否是一个好主意取决于你的真实问题,所以我会留给你决定。
答案 3 :(得分:0)
对于一个非常简单的修复,这个怎么样? (我只包括你需要改变的部分)
它的工作原理是因为您只能在取消定时器时访问堆栈变量。当然,你根本不需要在析构函数中回调处理程序,但我假设你的真实代码无论出于何种原因都需要这个。
~TimeBomb()
{
m_active = false;
executeStepFunction(boost::asio::error::interrupted);
m_runTimer.cancel();
cout << "Bomb defused @"<< hex << (int)this << endl;
}
void executeStepFunction(const boost::system::error_code& error)
{
// Canceled timer
if (error == boost::asio::error::operation_aborted)
{
return;
}
if (error == boost::asio::error::interrupted)
{
std::cout << "Timer aborted: " << error.message() << " @" << std::hex << (int)this << std::endl;
return;
}
...