5

我在我的应用程序中使用计时器队列,并将指向我自己的 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 派生自,我们只会从基类析构函数的集合中删除指针;如果我们已经开始销毁派生对象,损害已经造成。

干杯。

4

3 回答 3

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();
}

这样,您可以确保在您说之前不会开始破坏。

第二件事是,您必须等待最后一个计时器事件烧完。根据MSDN doc,您可以通过调用

DeleteTimerQueueTimer(TimerQueue, Timer, INVALID_HANDLE_VALUE)
于 2009-01-25T00:05:45.983 回答
2

当您调用 DeleteTimerQueueTimer 时,请确保您为完成事件传递了 INVALID_HANDLE_VALUE。这将阻塞你的析构函数,直到所有挂起的回调都完成或取消。

例如

virtual ~Timer()
   {
       ::DeleteTimerQueueTimer( timerQueue, handle, INVALID_HANDLE_VALUE );
   }

这意味着您的代码将阻塞,直到所有计时器回调完成或取消。然后,您可以照常进行销毁。不过要注意的一件事 - 您不能从同一个计时器回调中调用 deletetimerqueuetimer ,否则您将死锁。

我相信仅此一项就足以防止您遇到的竞争状况。

于 2011-04-05T19:39:20.597 回答
0

你几乎肯定不能用继承模型做到这一点。主要问题是,在输入基类构造函数时,派生对象已经无效,但计时器可能会触发,并且没有任何东西阻止它尝试虚拟函数调用,这将导致未定义的行为。

我认为这样做的方法是这样的包装器。关键是要确保在尝试分派“超时”事件时不存在竞争条件。

这个实现仍然有一个缺陷。当计时器对象开始被删除时,计时器事件可能正在等待。析构函数可以释放互斥锁,然后在计时器线程等待互斥锁时销毁互斥锁​​。我们已经阻止了发送“超时”事件的竞争,但是等待被销毁的互斥锁的线程的行为取决于互斥锁的实现。

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();
}
于 2009-01-25T00:07:32.690 回答