もくじ
https://tera1707.com/entry/2022/02/06/144447
やりたいこと
Windowsアプリで、タイマーで時間を測るときに、Windowsがスリープに入っている間も時間をカウントしたいと思った。
(C#で、Thread.Sleep(10 * 60 * 1000)
で10分待っている間に、Windowsを1分スリープさせて戻ってくると、実時間で11分経たないと、アプリのThread.Sleepが満了しないので、その1分遅れをせずに、10分で満了させたい)
WaitableTimerというのを使うとそういうことができるという話を聞いたので、試してみる。
試した結果
結論、できそうではある。
が、副作用がありそうな感じがするので、そこを把握して使わないといけなさそう。
→その副作用をしっかり把握するには、WindowsPCのハード的な部分がどうなってるのか、や、Windowsの設定等がどうなってるのか(スリープからWaitableTimerでレジュームできる構成なのか、設定なのか)を把握してないといけなさそうなのだが、それが手元にある自宅の古いPCだけでは実験/検証できなさそう&知識不足なので、今回はこれを使うのはやめようと思う。
ただせっかく実験したので、その時にやったメモは残そうと思う。
実験コード
https://github.com/tera1707/CppTimerTest
やったこと
下記のようなコードを書いてWaitableTimerを実験した。
基本の使い方は、
- CreateWaitableTimerして
- SetWaitableTimerをよぶ
下記のような感じ。
#include <windows.h> #include <stdio.h> HANDLE hTimer = NULL; LARGE_INTEGER liDueTime; long long sec = 5LL;//タイマ満了時間(秒) /// <param name="lpArg">SetWaitableTimerの第四引数で渡すパラメータ</param> /// <param name="dwTimerLowValue">時刻が載ってるらしい(使う気なし)</param> /// <param name="dwTimerHighValue">時刻が載ってるらしい(使う気なし)</param> VOID CALLBACK TimerAPCProc(LPVOID lpArg, DWORD dwTimerLowValue, DWORD dwTimerHighValue) { auto param = (int*)lpArg; printf("タイマ満了来ました。%d\r\n", *param); } // 満了時コールバック無し、シグナル待ちするパターン // fResume:SetWaitableTimerのタイマ満了時に、スリープをresumeさせるかどうか // →これをTRUEにしていると、スリープ/休止中もカウントを継続できる。で、スリープ中に満了したら、ハードやwindowsの設定が許せば、resumeするらしい。(手持ちPCでは起動しなかった)) void SetWaitableTimerWithoutCallback(HANDLE hTimer, LARGE_INTEGER liDueTime, BOOL fResume) { MessageBox(NULL, L"start", L"", MB_OK); if (!SetWaitableTimer(hTimer, &liDueTime, 0, NULL, NULL, fResume)) { printf("SetWaitableTimer 失敗 (%d)\r\n", GetLastError()); return; } printf("SetWaitableTimer 成功 (%d)\r\n", GetLastError()); // タイマー満了待ち if (WaitForSingleObject(hTimer, INFINITE) != WAIT_OBJECT_0) { printf("WaitForSingleObject 失敗 (%d)\r\n", GetLastError()); } else { printf("タイマーがシグナル状態になりました\r\n"); } MessageBox(NULL, L"stop", L"", MB_OK); } void SetWaitableTimerWithCallback(HANDLE hTimer, LARGE_INTEGER liDueTime, BOOL fResume) { MessageBox(NULL, L"start", L"", MB_OK); // 満了時コールバック有りパターン int param = 99; if (!SetWaitableTimer(hTimer, &liDueTime, 0, TimerAPCProc, ¶m, fResume)) { printf("SetWaitableTimer 失敗 (%d)\r\n", GetLastError()); return; } // 注意!Sleep()で待っていると、ここと同じスレッドで実行されることになるTimerAPCProcが呼べなくなる。 // WaitableTimerを使っている場合にスレッドをスリープさせたい場合は、SleepEx()で行う必要がある。 // (SleepEx()の第三引数はTRUE=アラート可能である必要がある。) // また、タイマ満了した結果、SleepEx()の待ちは終了する SleepEx(INFINITE, TRUE); MessageBox(NULL, L"stop", L"", MB_OK); } int main() { auto oneSec = -10000000LL;// 1=100nsec(ナノ秒)なので、これで1秒になる liDueTime.QuadPart = sec * oneSec; // タイマ作成 hTimer = CreateWaitableTimer(NULL, TRUE, L"aaa"); if (NULL == hTimer) { printf("CreateWaitableTimer 失敗 (%d)\r\n", GetLastError()); return 1; } #if 0 SetWaitableTimerWithoutCallback(hTimer, liDueTime, FALSE); #else SetWaitableTimerWithCallback(hTimer, liDueTime, FALSE); #endif return 0; }
SetWaitableTimerのパラメータ「fResume」は、SetWaitableTimerのタイマ満了時に、スリープをresumeさせるかどうかのフラグ。 これをTRUEにしていると、スリープ/休止中もカウントを継続できる。(今回やりたかったことはできる。)
で、スリープ中に満了したら、ハードやwindowsの設定が許せば、resumeするらしい。(手持ちPCでは起動しなかった))
(FALSEだと、スリープに入るとその間カウントが止まって、スリープ明け(resume後)に、カウント再開するので、スリープしてた分、満了までの時間が延びる)
問題
コールバックありのときに、実験コードのコンソールが終わってしまわないように、Sleep();で待たせていたのだが、それをすると、満了するはずの時間が経過しても、コールバックが呼ばれてくれない。
公式によると、Sleep()を使うとそうなってしまうので、SleepEx()
を使うべしとのこと。
そのあたりの関連かどうかわからないが、以前作ったダイアログベースのアプリの中で、今回の実験コードを持って行ってタイマをかけても、タイマ満了のコールバックを呼んでくれなかった。
(SetWaitableTimerしたあとに、普通にダイアログProcに戻っていった場合。ダイアログProcに戻る前に、ボタンを押したコマンドの中で、SleepEx(INFINITE, TRUE);をすると、満了コールバックに来てくれる。
→ダイアログProcの中で、コールバックを呼べない待ち方をしている??(想像)
TimerQueueTimerについて
SetWaitableTimerには、試した限り、上記のような問題があったのだが、以前別途試したTimerQueueTimer
だと、ダイアログベースでもうまく動作させることができた。
https://qiita.com/tera1707/items/9d07f179175068ceaa89
スリープ中にもタイマーカウント継続して、満了したらスリープ解除したい、とかの特殊な要件がなければ、個人的にはTimerQueueTimer
のほうを使った方がわかりやすそう&変なところでバグらなさそうに感じた。
備考
もしかしたら、Windowsの下記設定で、WaitableTimerでスリープからresumeするかどうか、設定できるのかも?(すみません、未確認)
参考
待機可能タイマー オブジェクトの使用
https://learn.microsoft.com/ja-jp/windows/win32/sync/using-waitable-timer-objects
非同期プロシージャ 呼び出しでの待機可能タイマーの使用
SetWaitableTimer 関数 (synchapi.h)
https://learn.microsoft.com/ja-jp/windows/win32/api/synchapi/nf-synchapi-setwaitabletimer
CreateWaitableTimerW 関数 (synchapi.h)
https://learn.microsoft.com/ja-jp/windows/win32/api/synchapi/nf-synchapi-createwaitabletimerw
Wake the PC from standby or hibernation
実験コード