2011-3-27 ここ暫く、原発の不安が続いて中々本業に集中出来ずにいた。
原発にはまだ予断を許さないが、決死の作業が続けられているのを思えば、本業に身を入れてより一層の経済活動に貢献せねばならないと思うこの頃。
今更語る事も少ないし、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 がキューに入りそうなんだけでも、そうはならない。これは妥当な設計と思う。
さて、アプリケーションとして 定周期の 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 を呼ぶ方法がある。
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 を削除しない」ですね。
良さげな感じですが、毎回タイマーを切り直すのは何だか無駄な気がします。キューに入っていない場合は不要だと思うので、キューに入っている時のみ切り直すように変えて見ます。
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