Home / Windows WM_TIMER

Windows WM_TIMER

2011-3-27 ここ暫く、原発の不安が続いて中々本業に集中出来ずにいた。
原発にはまだ予断を許さないが、決死の作業が続けられているのを思えば、本業に身を入れてより一層の経済活動に貢献せねばならないと思うこの頃。

今更な WM_TIMER

今更語る事も少ないし、WM_TIMER だけがタイマーではないが...

アプリケーション プログラマなら、処理が何らかの理由で停滞し、設定周期を越えてしまった場合、どうなるか判って使っている訳だが、(当たり前だ! 東電と一緒にすんな (`ヘ´))

実際、WM_TIMER とメッセージキューとの関係に確信が持てなかったので確かめる事にした。
適当な処で SetTimer を呼んでおいて次のようなタイマー プロシージャ周りを書く。なお、タイマーはウィンドウに関連付けていない。

const UINT uElapse = 500;   // タイマー周期 msec
const UINT dwSleep = 2300;  // スリープ時間 msec

static VOID CALLBACK
TimerProc(HWND /*hwnd*/, UINT /*uMsg*/,
          UINT_PTR /*idEvent*/, DWORD dwTime)
{
    static UINT flip = 0;
    static DWORD oldtime = 0;

    TRACE2("TimerProc %u %u\n", dwTime, dwTime - oldtime);
    oldtime = dwTime;

    if ((++flip & 7) == 0)
    {
        TRACE0("Sleep\n");
        ::Sleep(dwSleep);

        MSG msg;
        if (PeekMessage(&msg, NULL, WM_TIMER, WM_TIMER, 
            PM_NOREMOVE))
            //PM_REMOVE))
        {
            TRACE0("WM_TIMER in queue\n");
        }
    }
}

適当に8回に1度 Sleep を挟み、その間に WM_TIMER がキューに入っているかどうか PeekMessage で調べている。

--> 結果は次の通り、スリープさせた 2300 msec 近く遅れたコールの後に、定周期 500 より短い 187 msec でコールされる事になる。私のマシンはショボイので実際はもっと早い時間内にコールされると思う。

TimerProc 10050265 10050265
TimerProc 10050765 500
TimerProc 10051265 500
TimerProc 10051765 500
TimerProc 10052265 500
TimerProc 10052765 500
TimerProc 10053265 500
TimerProc 10053765 500
Sleep
WM_TIMER in queue
TimerProc 10056078 2313
TimerProc 10056265 187
TimerProc 10056765 500
TimerProc 10057265 500
TimerProc 10057765 500
TimerProc 10058265 500
TimerProc 10058765 500
TimerProc 10059265 500
Sleep
WM_TIMER in queue
TimerProc 10061578 2313
TimerProc 10061765 187
TimerProc 10062265 500
TimerProc 10062765 500
TimerProc 10063265 500

ここで、普通に考えると 2300 msec も休んでいるのだから、その間にもっと多くの WM_TIMER がキューに入りそうなんだけでも、そうはならない。これは妥当な設計と思う。

キューから WM_TIMER を取り除く

さて、アプリケーションとして 定周期の 500 msec より早く呼ばれるとまずいかどうかなんだけども、もし、それを回避したければ PM_NOREMOVE を PM_REMOVE へ変えてキューから削除してしまう手がある。

if (PeekMessage(&msg, NULL, WM_TIMER, WM_TIMER, 
    //PM_NOREMOVE))
    PM_REMOVE))

但し、複数のタイマーを作成している場合に1つだけを狙う事は出来ない(と思う)。
--> そうした結果は次の通り、定周期 500 の整数倍の 2500 msec 後のコールとなる。

TimerProc 10138265 10138265
TimerProc 10138765 500
TimerProc 10139265 500
TimerProc 10139765 500
TimerProc 10140265 500
TimerProc 10140765 500
TimerProc 10141265 500
TimerProc 10141765 500
Sleep
WM_TIMER in queue
TimerProc 10144265 2500
TimerProc 10144765 500
TimerProc 10145265 500
TimerProc 10145765 500
TimerProc 10146265 500
TimerProc 10146765 500
TimerProc 10147265 500
TimerProc 10147765 500
Sleep
WM_TIMER in queue
TimerProc 10150265 2500
TimerProc 10150765 500
TimerProc 10151265 500
TimerProc 10151765 500
TimerProc 10152265 500

上はまぁ、思い付きみたいものだが、理に適っているとは思う。

KillTimer -> SetTimer を繰り返す

他の方法として良く見かけるのが、タイマーイベントの毎に KillTimer -> SetTimer を呼ぶ方法がある。
PeekMessage を毎回監視するように外に出し、ハンドラの終わりに KillTimer -> SetTimer を書く。
トレース第2列には タイマーID 追加。

TimerProc(...
{
    ...
    MSG msg;
    if (PeekMessage(&msg, NULL, WM_TIMER, WM_TIMER, 
        PM_NOREMOVE))
        //PM_REMOVE))
    {
        TRACE0("WM_TIMER in queue\n");
    }

    if (timer_id)
    {
        ::KillTimer(0, timer_id);
        timer_id = ::SetTimer(NULL, 0, uElapse, TimerProc);
    }
}

--> そうした結果は次の通り。

          timer id
          ↓
TimerProc 28514 25698390 500
TimerProc 28513 25698890 500
Sleep
WM_TIMER in queue
TimerProc 28513 25701187 2297 ... キューに入っていた WM_TIMER
TimerProc 28511 25701687 500
TimerProc 28510 25702187 500

マニュアル通り、「KillTimer は、既にメッセージキューに入っている WM_TIMER を削除しない」ですね。

必要な時のみ KillTimer -> SetTimer

良さげな感じですが、毎回タイマーを切り直すのは何だか無駄な気がします。キューに入っていない場合は不要だと思うので、キューに入っている時のみ切り直すように変えて見ます。

TimerProc(...
{
    ...
    int InQ = 0;
    MSG msg;
    if (PeekMessage(&msg, NULL, WM_TIMER, WM_TIMER, 
        PM_NOREMOVE))
        //PM_REMOVE))
    {
        TRACE0("WM_TIMER in queue");
        InQ = 1;
    }

    if (timer_id)
    {
        if (InQ)
        {
            BOOL f = ::KillTimer(0, timer_id);
            UINT id = timer_id;
            timer_id = ::SetTimer(NULL, 0, uElapse, TimerProc);
            TRACE2(" ... KillTimer %u SetTimer %u\n", id, timer_id);
        }
    }
}
TimerProc 30075 22275484 500
TimerProc 30075 22275984 500
Sleep
WM_TIMER in queue ... KillTimer 30075 SetTimer 30074
TimerProc 30075 22278296 2312
TimerProc 30074 22278796 500
TimerProc 30074 22279296 500
TimerProc 30074 22279796 500
TimerProc 30074 22280296 500
TimerProc 30074 22280796 500
TimerProc 30074 22281296 500
TimerProc 30074 22281796 500
Sleep
WM_TIMER in queue ... KillTimer 30074 SetTimer 30073
TimerProc 30074 22284109 2313
TimerProc 30073 22284609 500
TimerProc 30073 22285109 500
TimerProc 30073 22285609 500
TimerProc 30073 22286109 500
TimerProc 30073 22286609 500
TimerProc 30073 22287109 500
TimerProc 30073 22287609 500
Sleep
WM_TIMER in queue ... KillTimer 30073 SetTimer 30072
TimerProc 30073 22289921 2312
TimerProc 30072 22290421 500

おしまい

後々、また実験するかも知れないのでソース置いておこう(Visual C++ 6.0 Shift-JIS CRLF)。
MainFrm.cpp
MainFrm.h


Home / Windows WM_TIMER
http://usskim.web.fc2.com/
inserted by FC2 system