5

アプリケーションでタイマー キューを使用しており、独自の C++ タイマー オブジェクトの 1 つへのポインターをコールバックへの「パラメーター」として渡します (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() の呼び出し前にタイマー オブジェクトが破棄された場合、コールバックが存在しないオブジェクトの仮想メソッドを呼び出すため、アプリがクラッシュするという微妙な競合状態があります。さらに悪いことに、削除中です。

マルチスレッド呼び出しを制御するためにミューテックスを配置していますが、それでも問題が発生します。

コールバック パラメータとしてオブジェクト ポインタを使用することは本当に良い考えですか? スレッド間の同期が保証されていないため、私には悪臭がします。

より良い解決策はありますか?他の人は何をしますか?

発生することの 1 つは、すべての単一の 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();
}

このようにして、あなたが言うまで破壊が始まらないようにすることができます.

2 つ目は、最後のタイマー イベントが燃え尽きるまで待たなければならないことです。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 );
   }

これは、すべてのタイマー コールバックが完了するかキャンセルされるまで、コードがブロックされることを意味します。その後、通常どおり破壊を続行できます。ただし、注意すべき点が 1 つあります。同じタイマー コールバックから deletetimerqueuetimer を呼び出すことはできません。そうしないと、デッドロックが発生します。

発生している競合状態を防ぐには、これだけで十分だと思います。

于 2011-04-05T19:39:20.597 に答える
0

ほとんどの場合、継承モデルではこれを行うことはできません。主な問題は、基本クラスのコンストラクターが入力されるまでに、派生オブジェクトが既に無効になっているにもかかわらず、タイマーが起動し、未定義の動作が発生する仮想関数呼び出しの試行を停止することができないことです。

やり方はこのようなラッパーだと思います。ポイントは、「タイムアウト」イベントをディスパッチしようとして競合状態が発生しないようにすることです。

この実装にはまだ 1 つの欠陥があります。タイマー オブジェクトの削除が開始されると、タイマー イベントが待機している可能性があります。タイマー スレッドがミューテックスで待機している間に、デストラクタがミューテックスを解放し、ミューテックスを破棄する可能性があります。「タイムアウト」イベントのディスパッチで競合を防止しましたが、破棄されたミューテックスを待機しているスレッドの動作は、ミューテックスの実装に依存します。

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 に答える