Windows待機処理はイベントを使え【15.6msポーリングの罠】
まず結論
時間を待つなら timer、出来事を待つなら event。
Sleep(1)は「1ms 後に正確に起きる」意味ではない。System clock の粒度(約15.6ms)とスケジューリング遅延の影響を受ける- 仕事の到着や I/O 完了を待つなら、timer ではなく event を待つほうが遅延・CPU・電力のすべてで有利
- timeout が過ぎても、thread は「ready になる」だけで即実行は保証されない
- 時間そのものが条件のときだけ timer を使う
実務での使い分け
| 待ちたいもの | よくない例 | まずの選択 |
|---|---|---|
| キューに仕事が入ること | Sleep(1) で TryPop |
event / semaphore |
| I/O が完了すること | timer で状態を見に行く | overlapped I/O の event / IOCP |
| 停止要求が来ること | 100ms ごとに flag を見る | stop event / cancellation |
| 同一プロセス内の値変化 | while (flag==0) Sleep(1) |
WaitOnAddress |
| 時刻が来ること | event に無理やり寄せる | timer / waitable timer |
何が問題なのか
timed wait は system clock の粒度に縛られる
Sleep や timeout 付き wait の精度は、system clock resolution に依存する。指定したミリ秒がそのまま保証されるわけではない。
期限が来てもすぐ実行されない
timeout 後、thread は ready になるが、他の thread や priority、CPU の idle state、DPC/ISR、lock 競合の影響で即実行は保証されない。
Sleep(1) は 1ms 周期のループにならない
while (!g_stop) {
Step();
Sleep(1);
}
このループは実際には:
Step()の実行時間が毎回加算されるSleep(1)自体が粒度に引っ張られる- 起きてもすぐ走れるとは限らない
なぜイベント待機が有利か
待ちの終了条件が「時間切れ」ではなく「signal」になる
- timer wait: 何も起きていなくても一定時間で起き、それから「何かあったか」を確認
- event wait: 何かが起きた側が signal し、signal されたら待ちが満たされる
何を待ちたいかで道具を選ぶ
flowchart LR
start["待っている thread"] --> q{"待っているものは?"}
q -- "時間" --> timer["timer / waitable timer"]
q -- "仕事の到着" --> event["event / semaphore / condition variable"]
q -- "値の変化" --> addr["WaitOnAddress"]
q -- "I/O 完了" --> io["completion / event"]
q -- "停止要求" --> stop["stop event / cancellation"]
典型的なアンチパターンと改善例
アンチパターン: Sleep(1) でキューをポーリング
for (;;) {
if (g_stop) break;
WorkItem item;
if (TryPop(item)) { Process(item); continue; }
Sleep(1);
}
問題点:
- queue が空でも定期的に起きる
- latency が timer 粒度に引っ張られる
- 電力効率が悪い
改善例: WaitForMultipleObjects
HANDLE waits[2] = { _stopEvent, _workEvent };
for (;;) {
DWORD rc = WaitForMultipleObjects(2, waits, FALSE, INFINITE);
if (rc == WAIT_OBJECT_0) return; // stop
if (rc == WAIT_OBJECT_0 + 1) DrainQueue();
}
ポイント:
Sleep(1)が消えている- item 到着時に producer が
SetEventする - worker は
stopとworkを同時に待つ
C#/.NET でも同様の問題が起きる
while (!stoppingToken.IsCancellationRequested) {
if (_queue.TryDequeue(out var item)) { await ProcessAsync(item); continue; }
await Task.Delay(1, stoppingToken); // これも本質は polling
}
同一プロセスなら WaitOnAddress も候補
同じプロセス内で単に「ある値が変わるまで待ちたい」だけなら WaitOnAddress が軽量で便利。
- プロセス間や一般的な待機対象 → event / semaphore / waitable object
- 同一プロセスの軽い値変化 →
WaitOnAddress
それでも timer を使う場面
時間そのものが条件のときは timer が正しい:
- 5秒ごとに metrics を送る
- 200ms 後に retry する
- 1分ごとにキャッシュを掃除する
- 期限時刻まで待って timeout にする
この場合は Sleep を雑に積むより waitable timer を使うほうが意味が明確。
timeBeginPeriod を常用しない
精度が気になるからと timeBeginPeriod(1) を足すのは避ける:
- power/performance のコストがある
- 最近の Windows では挙動が複雑
- 根本原因を直していないことが多い
レビュー時のチェックリスト
Sleep(1)やTask.Delay(1)で様子見ループを作っていないか- 本当は queue 到着や I/O 完了を待っているのに timer poll していないか
- producer 側から signal できる設計になっているか
stopとworkを 1回の wait でまとめて待てないか- 同一プロセスの値変化なら
WaitOnAddressで書けないか - timer を使っている場所で、本当に待ちたいものが「時間」なのか
まとめ
Windows で短い timer wait を使ったポーリング設計は、timer 粒度と scheduler の影響で見た目ほど正確ではない。
時間を待つなら timer、出来事を待つなら event。 この線引きがはっきりするだけで、latency が読みやすくなり、無駄な periodic wakeup が減り、電力効率も改善し、コードの意図が明確になる。
参考資料
- Sleep function (Win32)
- Wait Functions
- WaitForSingleObject function
- Event Objects (Synchronization)
- Using Event Objects
- WaitOnAddress function
- WakeByAddressSingle function
- timeBeginPeriod function
- CreateWaitableTimerExW function
- SetWaitableTimer function
- Thread.Sleep Method (.NET)
- Results for the Idle Energy Efficiency Assessment
関連する記事
同じタグを共有する最新の記事です。さらに近い話題で知識を深められます。
未想定例外発生時の対応判断フロー
想定していない例外が発生したとき、アプリを終了すべきか継続すべきかを判断するための実用的な指針をまとめた記事です。失敗単位、共有状態、外部副作用、ネイティブ境界の観点から3段階の選択肢と判断表で整理し、設計判断に直接使えます。
Windowsアプリのセキュリティチェックリスト
WPF・WinForms・WinUI・C++・C# など Windows アプリ開発で最低限外したくないセキュリティ項目を、権限・署名・秘密情報・通信・入力・DLL・ログの観点でチェックリスト化し、リリース前に何を確認すべきか整理した記事です。
.NETの定期実行タイマー3種を使い分ける
PeriodicTimer・System.Threading.Timer・DispatcherTimer の違いと、async 処理・ThreadPool callback・WPF の UI 更新でどう使い分けるかを、判断表とコード例で整理した .NET 定期実行の入門記事です。
デスクトップアプリにGeneric Hostを導入する
WPF や WinForms の常駐アプリで、起動・停止・例外処理・定期処理が UI に染み出してきたら読みたい記事。Generic Host と BackgroundService を導入して責務を整理し、graceful shutdown まで設計に組み込む考え方を、最...
FileSystemWatcherの安全な使い方
FileSystemWatcher のイベントは完了通知ではないという前提に立ち、取りこぼし、重複通知、完了判定の落とし穴を整理し、再スキャン要求への畳み込み、原子的 claim、idempotency までの設計指針をまとめます。
関連トピック
このテーマと近いトピックページです。記事を起点に、関連するサービスや他の記事へ進めます。
Windows技術トピック
Windows 開発、不具合調査、既存資産活用の技術トピックをまとめた入口です。
UI スレッド / タイマーテーマ
WPF / WinForms、UI スレッド、async/await、タイマー設計を整理するトピックです。
このテーマがつながるサービス
この記事は次のサービスページにつながります。近い入口からご覧ください。
Windowsアプリ開発
業務アプリ、装置連携、通信ツールなどの Windows ソフト開発を支援します。
技術相談・設計レビュー
改修方針、設計レビュー、既存資産の扱いを整理するための技術相談です。