我正在我的应用程序中使用计时器队列,并将指针传递给我自己的C ++ Timer对象之一作为回调的“参数”(在CreateTimerQueueTimer中)。然后我在回调中的对象上调用虚方法。
Timer对象的析构函数将确保使用DeleteTimerQueueTimer()取消计时器。
static void callback( PVOID param, BOOLEAN timerOrWaitFired )
{
Timer* timer = reinterpret_cast< Timer* >( param );
timer->TimedOut();
}
class Timer
{
public:
Timer();
virtual ~Timer()
{
::DeleteTimerQueueTimer( handle );
}
void Start( double period )
{
::CreateTimerQueueTimer( &handle, ..., &callback, this, ... );
}
virtual void TimedOut() = 0;
...
};
然而,有一个微妙的竞争条件,如果已经调用了回调,但是在调用TimedOut()之前销毁了计时器对象,应用程序崩溃,因为回调调用了虚拟对不存在的对象的方法。或者更糟糕的是,它被删除了。
我确实有互斥锁来控制多线程调用,但我仍然遇到问题。
使用对象指针作为回调参数真的是个好主意吗?由于不保证线程之间的同步,它对我来说闻起来很糟糕。
有更好的解决方案吗?其他人做了什么?
发生的一件事是保留一组指向每个Timer实例的指针(添加构造函数,在析构函数中删除)。但我认为这不会起作用,因为如果从中派生出Timer,我们只会从基类析构函数中的集合中删除指针;如果我们已经开始销毁派生对象,则已经完成了损坏。
干杯。
答案 0 :(得分:3)
使用对象指针作为回调函数参数的概念本身并不坏。但是,您显然需要在最后一次回调退出后开始销毁。
所以,我不会让Timer摘要并从中衍生出来。我会使用另一个抽象类TimerImpl
并使Timer
类使用TimerImpl
实例:
class Timer
{
TimerInstance* impl;
void TimeOut() { impl->TimeOut(); }
public:
~Timer() {
... make sure the timer has ended and wont fire again after this line...
delete impl;
}
}
struct TimerImpl
{
virtual void TimeOut()=0;
virtual ~TimerImpl();
}
这样,您可以确保在您说完之后才会开始销毁。
第二件事是,你必须等待最后一个计时器事件烧坏。根据{{3}},你可以通过调用
来实现DeleteTimerQueueTimer(TimerQueue, Timer, INVALID_HANDLE_VALUE)
答案 1 :(得分:2)
当您调用DeleteTimerQueueTimer时,请确保传递INVALID_HANDLE_VALUE 完成活动。这将阻止您的析构函数,直到所有待处理的回调都完成或取消。
e.g。
virtual ~Timer()
{
::DeleteTimerQueueTimer( timerQueue, handle, INVALID_HANDLE_VALUE );
}
这意味着您的代码将阻塞,直到所有计时器回调完成或取消。然后你可以照常进行破坏。有一点需要注意 - 你不能从同一个计时器回调调用deletetimerqueuetimer,否则你将死锁。
我认为仅此一项就足以防止你遇到的竞争状况。
答案 2 :(得分:0)
使用继承模型几乎肯定无法做到这一点。主要的问题是,在输入基类构造函数时,派生对象已经无效,但是计时器可能会触发,并且没有任何操作停止它尝试虚函数调用,这将导致未定义的行为。
我认为这样做的方法是这样的包装。关键在于确保在尝试发送“超时”事件时没有竞争条件。
这个实现仍有一个缺陷。当计时器对象开始被删除时,计时器事件有可能正在等待。析构函数可能会释放互斥锁,然后在定时器线程等待互斥锁时销毁互斥锁。我们已经阻止了调度'timed out'事件的竞争,但等待被破坏的互斥锁的线程的行为取决于互斥锁的实现。
static void callback( PVOID param, BOOLEAN timerOrWaitFired );
class TimerWrapper
{
public:
/* Take reference to std::auto_ptr to ensure ownership transfer is explicit */
TimerWrapper( std::auto_ptr<Timer>& timer ) : timer_(timer)
{
::CreateTimerQueueTimer( &htimer_, ..., callback, this, ... );
}
void TimedOut()
{
ScopedGuard guard( mutex_ );
if( timer_.get() )
timer_->TimedOut();
}
~TimerWrapper()
{
::DeleteTimerQueueTimer( htimer_, ... );
ScopedGuard guard( mutex_ );
timer_.reset();
}
private:
Mutex mutex_;
std::auto_ptr<Timer> timer_;
HANDLE htimer_;
};
static void callback( PVOID param, BOOLEAN timerOrWaitFired )
{
TimerWrapper* timer = reinterpret_cast< TimerWrapper* >( param );
timer->TimedOut();
}