更新履歴(5件・最終更新 2026年08月02日)
この記事に加えた変更の記録です。アーカイブした更新前のバージョンは、DOI付きの固定URLから読めます。
- 記事の冒頭に「この記事の知識マップ」節を追加しました。本文で扱っている概念とその関係を、要約・図・詳細ページへのリンクにまとめたものです。本文の主張は変えていません。
- 外部レビュー(1283件)への対応として本文を更新しました。個々の変更内容は、この下の履歴を参照してください。
- 電源通知のコールバックが例外を外へ出し得る形でした。このメソッドはOSから直接呼ばれるため、managedの例外が境界を越えると受け止める呼び出し元が居らず、プロセスごと落ちます。ログの書き込み先が一杯、collectorが破棄済みといった実際に起こりうる失敗で、スリープのたびにアプリが死ぬことになります。本体を`try`/`catch`で囲み、ログ出力自体の失敗も飲み込んで必ずコードを返すようにしました。
- `PowerUnregisterSuspendResumeNotification`の戻り値を検査するようにしました。解除できていないのに登録ハンドルを捨てると、OS側には登録が残ったまま追跡手段だけが失われ、インスタンスが回収された時点でコールバックのデリゲートも消えます。失敗時はハンドルを残し、デリゲートをプロセスの寿命まで生かす形にしました。
- 冒頭を「スリープの種類と止まっている間に起きること」「スリープさせない設計」「スリープに追従する設計」の3つに分けました。あわせてタイマーや待機APIの種類ごとにスリープ中の扱いと復帰後の挙動を並べた表と、ウィンドウを持たないアプリで復帰を受け取るコード例を追加しています。
- 初版公開
この記事を引用する(DOI: 10.5281/zenodo.21590050)
この記事はZenodoにアーカイブされています。常に最新版へ解決されるDOIと、いま表示している版に固定されたDOIの両方を下に示します。
小村 豪(2026)「スリープ・休止・Modern Standbyと長時間稼働アプリ ── 「夜中に止まっていた」を設計で防ぐ」合同会社小村ソフト. https://doi.org/10.5281/zenodo.21590050 https://staging.comcomponent.com/blog/windows-sleep-modern-standby-long-running-apps/
- DOI(最新版)
- 10.5281/zenodo.21590050
- DOI(この版)
- 10.5281/zenodo.21733021
「監視アプリを夜通し動かしていたはずが、朝見たらグラフが深夜1時で止まっていた」「デスクトップPCでは何か月も安定していた収集ツールが、ノートPCに置き換えた途端に記録が歯抜けになった」── 長時間動き続ける業務アプリの相談で、この症状は定番中の定番です。ログを見ると例外もクラッシュもなく、ただ数時間分の記録がすっぽり抜けている。犯人はアプリのバグではなく、Windowsの電源管理であることが非常に多いのです。
厄介なのは、「スリープ」と一口に言っても中身が1種類ではないことです。従来のS3スリープ、休止状態(S4)、そして近年のノートPCで主流のModern Standby(S0 low power idle)では、アプリから見える止まり方も、対策の効き方も違います。特にModern Standbyは「スリープ中もシステムは動いている」という触れ込みから誤解されがちですが、デスクトップアプリはむしろ積極的に止められます。
この記事では、スリープの種類とスリープ中のアプリの挙動を最低限だけ押さえたうえで、「スリープをさせない」「スリープを前提にする」「決まった時刻に起こす」という3つの設計をどう使い分けるかを、実装例と判断表つきで整理します。
1. まず結論
結論は多いので、前提(1.1)・スリープを「させない」設計(1.2)・スリープに「追従する」設計(1.3)の3つに分けて置きます。1.1を読んだうえで、自分のアプリが1.2側か1.3側かを決めてから本文に進むのが近道です(判断表は第7章)。
1.1. 前提 ── スリープの種類と、止まっている間に起きること
- Windowsのスリープには従来のS3スリープ・休止状態(S4)・Modern Standby(S0 low power idle)があり、Modern Standby対応機はS1〜S3をサポートしません。自分のPCがどれかは
powercfg /aで確認できます。123 - Modern Standby中もデスクトップアプリは動き続けません。DAM(Desktop Activity Moderator)がデスクトッププロセスのスレッドをサスペンドします(セッション0のサービスはスロットリング)。「S0だから動くはず」という前提で設計しないでください。4
- スリープ中はスレッドが実行されず、タイマーの期限の数え方もAPIの世代で異なります。Windows 8以降、相対指定のタイマーや待機(
SetWaitableTimerの相対指定、SleepEx等)はスリープ中カウントが進まず、残り時間を復帰後に持ち越します。56 .NETのタイマーは .NET 10 まではスリープ時間を数える実装(スリープ中に期限が来ていれば復帰直後に発火)で、.NET 11 から数えない方式に変わる予定です(.NET 11 は本記事執筆時点で未リリース。3.1の但し書きを参照)。6 経過時間APIも、スリープ時間を含むもの(GetTickCount、QueryPerformanceCounter=Stopwatch)と含まないもの(QueryUnbiasedInterruptTime)が混在します。78 - スリープ中にアイドルになったTCP接続は、NAT・ファイアウォール・ロードバランサーなどの中間機器のアイドルタイムアウトで黙って破棄されていることがあります(例: Azure Load Balancerの既定は4分で通知なしドロップ)。復帰後は再接続前提で設計します。94
1.2. スリープを「させない」設計(第4章)
- スリープを抑止する正攻法は
SetThreadExecutionState(ES_CONTINUOUS | ES_SYSTEM_REQUIRED)です。ただし効くのは無操作タイムアウト起因の自動スリープだけで、ユーザーが電源ボタンやふた閉じで指示したスリープは防げません。必要な区間だけ設定し、終わったら必ずクリアします。1011 - 抑止が効いているかは
powercfg /requestsで確認できます。PowerCreateRequest系APIなら要求に理由文字列を付けられ、この一覧に表示されるため運用調査に強くなります。121314 - 24時間動かし続ける装置用PCのように「そもそもスリープさせない」が要件なら、アプリの抑止APIではなく電源設定で無効化するのが本筋です(具体的な設定箇所は7.1)。
1.3. スリープに「追従する」設計(第5章・第6章)
- スリープを前提にするなら、.NETでは
SystemEvents.PowerModeChanged、Win32ではWM_POWERBROADCAST(PBT_APMSUSPEND / PBT_APMRESUMEAUTOMATIC)で検知します。サスペンド通知の猶予は1アプリあたり約2秒しかなく、バッテリー切迫時は通知なしでスリープすることもあります。15161718 - ウィンドウを持たないコンソール・サービス型のアプリは、
PowerRegisterSuspendResumeNotificationでHWNDなしに同じ通知を受け取れます(最小例は5.1)。19 - 決まった時刻に確実に動かすなら、タスクスケジューラの「タスクの実行時にスリープを解除する」(WakeToRun)が第一候補です。タスク完了までシステムを起きたままにしてくれます。ただし電源オプションのウェイクタイマー許可に依存するため、実機での起床確認が必須です。2021
この記事の知識マップ
Windowsのスリープには従来のS3スリープ・休止状態(S4)・Modern Standby(S0 low power idle)があり、Modern Standby機でもデスクトップアプリはDAM(Desktop Activity Moderator)にスレッドをサスペンドされ、見かけ上動いていてもアプリは止まる。スリープ中にアイドル化したTCP接続は中間機器のタイムアウトで黙って破棄され、復帰後は再接続が前提になる。抑止はSetThreadExecutionStateや電源要求APIで宣言し、powercfg /requestsで確認できるが、ユーザー操作によるスリープやバッテリー駆動のModern Standbyでの打ち切りは防げない。スリープ前提の設計ではPowerModeChangedやPowerRegisterSuspendResumeNotificationで復帰を検知して再接続・再スケジュールし、定時処理はWakeToRunで起床させるのが定石となる。
flowchart LR
accTitle: スリープとModern Standbyの知識マップ
accDescr: S3スリープ・休止・Modern StandbyそれぞれでアプリがDAMによりどう止まるか、SetThreadExecutionStateや電源要求による抑止、SystemEvents.PowerModeChangedによる復帰対応、WakeToRunによる起床実行の関係を示す図
modern_standby["Modern Standby対応デバイス"]
dam["DAM(Desktop Activity Moderator)"]
s3_sleep["S3スリープ(従来のスリープ)"]
tcp_idle_timeout_drop["中間機器のアイドルタイムアウトによるTCP接続破棄"]
setthreadexecutionstate["SetThreadExecutionState"]
powercfg_requests["powercfg /requests"]
power_request_api["電源要求API(PowerCreateRequest等)"]
systemevents_powermodechanged["SystemEvents.PowerModeChanged"]
windows_service["Windowsサービス"]
wm_powerbroadcast["WM_POWERBROADCAST"]
powerregistersuspendresumenotification["PowerRegisterSuspendResumeNotification"]
waketorun["WakeToRun(タスクスケジューラの起床実行)"]
allow_wake_timer_setting["電源オプションの「スリープ解除タイマーの許可」"]
task_scheduler["タスクスケジューラの無人実行"]
wakeable_timer["ウェイク可能タイマー(SetWaitableTimerのfResume)"]
powercfg_a["powercfg /a"]
hibernate_s4["休止状態(S4)"]
modern_standby -->|"両立しない"| s3_sleep
modern_standby -->|"利用する"| dam
dam -.->|"原因になり得る"| tcp_idle_timeout_drop
s3_sleep -.->|"原因になり得る"| tcp_idle_timeout_drop
setthreadexecutionstate -.->|"防止する"| s3_sleep
setthreadexecutionstate -->|"で確認できる"| powercfg_requests
power_request_api -->|"で確認できる"| powercfg_requests
power_request_api -.->|"両立しない"| modern_standby
systemevents_powermodechanged -.->|"両立しない"| windows_service
systemevents_powermodechanged -->|"利用する"| wm_powerbroadcast
powerregistersuspendresumenotification -->|"推奨される対応"| windows_service
systemevents_powermodechanged -->|"推奨される対応"| tcp_idle_timeout_drop
waketorun -->|"前提とする"| allow_wake_timer_setting
waketorun -->|"利用する"| task_scheduler
wakeable_timer -.->|"前提とする"| setthreadexecutionstate
modern_standby -->|"で確認できる"| powercfg_a
s3_sleep -->|"で確認できる"| powercfg_a
hibernate_s4 -->|"で確認できる"| powercfg_a
waketorun -.->|"前提とする"| s3_sleep
図の実線は常に成り立つ関係、破線は条件付きの関係です(成立条件は詳細ページの各関係の説明に記載)。関係すべての一覧(全19件、根拠・確度つき)と主要概念の定義は知識マップ詳細ページにまとめています。データ: JSON-LD / Turtle
2. Windowsのスリープは1種類ではない
最初に、対策を考える前提となる3つの状態だけ押さえます。
| 状態 | 通称 | 中身 | アプリから見ると |
|---|---|---|---|
| S3 | 従来のスリープ | CPU停止、RAMだけ通電して状態保持 | 計算処理は一切走らない1 |
| S4 | 休止状態 | メモリ内容を休止ファイルに書いて電源断 | 同上。復帰はファイルからの復元1 |
| S0 low power idle | Modern Standby | システムは低電力で部分的に稼働し続け、瞬時に復帰 | デスクトップアプリはDAMに止められる24 |
S3とS4は「システムが計算タスクを何も実行していない、見かけ上オフの状態」です。1 一方Modern Standbyは、スマートフォンの電源モデルに近い方式で、画面オフ中もネットワーク接続を維持したまま低電力で待機し、電源ボタンから1秒未満で復帰します。Modern Standbyをサポートする機種はS1〜S3を使いません。222
ここで重要なのが、Modern Standby対応機に載っているDAM(Desktop Activity Moderator)です。DAMはスタンバイ突入時にデスクトップアプリの実行をS3スリープと同等になるよう抑え込む仕組みで、対話セッションのプロセスは全スレッドがサスペンドされ、セッション0のサービスはスロットリング(大半の時間サスペンドされ、断続的にだけ実行)されます。4 つまり「Modern Standbyならスリープ中もアプリが動くのでは」という期待は逆で、アプリ視点ではS3と同じく止まる、しかもS3と違って「システム自体は動いているのにアプリだけ止まる」ため、時刻のずれやタイマー挙動の不整合として現れる、と理解しておくのが安全です。4
自分のPC(顧客の現場のPC)がどちらの方式かは、管理者不要で次のコマンドで確認できます。3
> powercfg /a
以下のスリープ状態がこのシステムで利用可能です:
スタンバイ (S0 低電力アイドル) ネットワークに接続されています
休止状態
...
スタンバイ (S3) と出ればS3機、S0 低電力アイドル と出ればModern Standby機です。「ノートPCに変えたら記録が抜けるようになった」という相談では、まずこれを確認します。
3. スリープ中・復帰後にアプリはどうなるか
3.1. スレッドとタイマー
スリープ中(S3/S4、およびDAMサスペンド中)はスレッドが実行されません。14 見落とされがちなのは、タイマーや待機の「期限」がスリープ時間をどう数えるかで、これはAPIの層によって扱いが分かれます。次章の経過時間APIの表と同じ形で並べると、こうなります。
| タイマー・待機の種類 | スリープ中の扱い | 復帰後の挙動 |
|---|---|---|
Win32の相対タイマー(SetWaitableTimer / SetWaitableTimerEx の相対指定) |
Windows 8以降はカウントが進まない(Windows 7以前は低電力状態の時間も含めて進んでいた)5 | 残り時間ぶん待ってから発火する5 |
タイムアウト指定の待機API(SleepEx / WaitForMultipleObjectsEx など) |
Windows 8以降は非稼働時間を数えない6 | 残り時間を持ち越して待機を続ける6 |
.NETのマネージドタイマー(System.Threading.Timer 等) |
.NET 10までは数える(GetTickCount64ベース)、.NET 11からは数えない(QueryUnbiasedInterruptTimeベース)予定6 | .NET 10までは期限を過ぎていれば復帰直後に発火、.NET 11からは残り時間を待ってから発火する予定6 |
ウェイク可能タイマー(SetWaitableTimer の fResume にTRUE) |
システム自体を起床させる23 | 起床して発火するが、無人アイドルタイマー(最低2分)の間に「使用中」を宣言しないと再スリープする(第6章)23 |
3行目の変更ドキュメントは、Environment.TickCount64 の基盤が変わることによる破壊的変更として説明されており、ドキュメント自身が「復帰直後にタイマーが発火しなくなるコードがあり得る」と注意しています。6
.NET 11 に関する記述について
.NET 11 は本記事の執筆時点(2026年7月)でまだリリースされていません。.NETは毎年11月にメジャーリリースされる年次サイクルで、直近の .NET 10 は2025年11月11日公開です。24 本記事の .NET 11 の挙動は、Microsoftが公開している破壊的変更ドキュメント(プレビュー段階の情報)に基づく予定であり、GA後に必ず最新のドキュメントで確認してください。6
つまり「10秒間隔の System.Threading.Timer で計測していたアプリが8時間スリープした」場合、8時間分がまとめて発火することはどのみちありませんが、復帰直後に1回すぐ発火するのか、残り時間を待ってから発火するのかは、APIの層とランタイムのバージョン次第です。どちらにせよスリープ中のサンプルは欠測になります。「復帰直後の発火」に依存して立て直し処理を組むのではなく、第5章の復帰イベントで明示的に再スケジュールするのが安全です。
3.2. 経過時間の計測がずれる
経過時間APIには、スリープ時間を含むものと含まないものが混在しています。
| API | スリープ時間 |
|---|---|
| GetTickCount / GetTickCount64 | 含む7 |
| QueryPerformanceCounter(= .NET の Stopwatch の基盤) | 含む(standby・hibernate・connected standby)8 |
| QueryUnbiasedInterruptTime | 含まない(working stateの時間のみ)257 |
| Environment.TickCount / TickCount64 | .NET 10 まで含む(GetTickCount64ベース)、.NET 11 から含まない(QueryUnbiasedInterruptTimeベース)に変更6 |
「Stopwatchで10秒たったら次のサンプル」というコードは、スリープをまたぐと「8時間と10秒たった」ことになりますし、逆にTickCountベースの経過判定は .NET 11 移行で挙動が変わります。「処理にかかった時間」はStopwatch、「次に動くべき壁時計時刻」は DateTime/DateTimeOffset で管理し、復帰イベント(第5章)で基準を取り直す、という役割分担にしておくと、スリープにもランタイム更新にも振り回されません。短周期タイマーの設計自体の話は「WindowsでSleep(1)よりイベント待機を優先すべき理由」で扱っています。
3.3. TCP接続は「黙って」死んでいる
スリープ中、アプリは通信できないので接続はアイドルになります。問題は経路上の機器です。NATやファイアウォール、ロードバランサーといった中間機器はアイドルなフローをタイムアウトで破棄しますが、多くの構成では破棄を両端に通知せず黙って落とします。たとえばAzure Load Balancerの既定動作は「アイドルタイムアウト(既定4分)に達したフローをサイレントにドロップ」です。9 社内のルーターや装置側の組み込みTCPスタックにも同種のタイムアウトはあります。
その結果、復帰後のアプリのソケットは一見正常なまま、次の送信でエラーになるか、応答待ちでタイムアウトまで固まります。DAMのドキュメントも、プロセスサスペンドが接続の寿命やハンドシェイクに与える影響を考慮するよう明記しています。4 さらにModern Standby機では、バッテリー駆動時は既定でスリープ中のネットワーク活動そのものが静止されます(Adaptive Connected Standby)。26 復帰イベントを受けたら接続は疑って破棄・再接続するのが定石です。接続が「生きているように見えて死んでいる」問題の切り分けは「TCP再送で産業用カメラ通信が止まる原因と切り分け」も参考にしてください。
4. スリープを「させない」 ── SetThreadExecutionStateと電源要求
計測中の数時間だけはスリープされては困る、という場合の正攻法が SetThreadExecutionState です。C#からはP/Invokeで呼びます。
using System.Runtime.InteropServices;
internal static class PowerGuard
{
[Flags]
private enum EXECUTION_STATE : uint
{
ES_CONTINUOUS = 0x80000000,
ES_SYSTEM_REQUIRED = 0x00000001,
ES_DISPLAY_REQUIRED = 0x00000002,
}
[DllImport("kernel32.dll", SetLastError = true)]
private static extern EXECUTION_STATE SetThreadExecutionState(EXECUTION_STATE esFlags);
/// <summary>計測開始時に呼ぶ: 自動スリープを抑止する(Endと同じスレッドから呼ぶこと)</summary>
public static void Begin() =>
SetThreadExecutionState(EXECUTION_STATE.ES_CONTINUOUS |
EXECUTION_STATE.ES_SYSTEM_REQUIRED);
/// <summary>計測終了時に必ず呼ぶ: 抑止を解除する(Beginと同じスレッドから呼ぶこと)</summary>
public static void End() =>
SetThreadExecutionState(EXECUTION_STATE.ES_CONTINUOUS);
}
押さえるべき仕様は次のとおりです。
ES_CONTINUOUSを付けると、次にES_CONTINUOUS付きで呼び直すまで効果が持続します。付けない場合はアイドルタイマーを1回リセットするだけなので、持続させたければ定期的に呼び続ける必要があります。10ES_SYSTEM_REQUIREDはシステムのスリープを、ES_DISPLAY_REQUIREDはディスプレイオフを抑止します。バックグラウンド計測なら前者だけで十分です。画面は消えてよいのにES_DISPLAY_REQUIREDまで立てるのは電力の無駄です。10- ユーザーが電源ボタンを押した・ふたを閉じたことによるスリープは防げません。この関数が効くのは無操作タイムアウト起因の自動スリープだけです。ユーザーの明示操作は尊重すべきである、と公式ドキュメントにも明記されています。10
- システムは
SetThreadExecutionStateを呼んだスレッドを数えており、カウントがゼロになりユーザー入力もなければスリープに入ります。11 プロセスが異常終了すれば抑止も消えるので、「抑止したまま解除し忘れて永遠にスリープしないPC」になる心配は再起動すれば解消しますが、逆に言えばクラッシュ耐性の担保にはなりません。 - 名前のとおり、この関数が設定するのは呼び出したスレッドの実行状態です。10 設定と解除は同じスレッドで行ってください。
async/awaitの継続は別のスレッドプールスレッドで実行されることがあるため、awaitを挟んでBegin()とEnd()を呼ぶ実装では、解除がスリープ抑止を立てたのとは別のスレッドで空振りし、元のスレッドが生きている間ずっと抑止が残り続けます。UIスレッドのように同一性が保証されるスレッドに固定して呼ぶのが安全で、スレッドをまたぐ設計が必要ならこの後で触れるハンドルベースの電源要求APIを使ってください。
Windows 7以降には、同じ目的のより新しいAPIとして電源要求(PowerCreateRequest / PowerSetRequest / PowerClearRequest)もあります。こちらは要求作成時に REASON_CONTEXT で理由の文字列を渡せるのが実務上の利点で、要求種別には PowerRequestSystemRequired のほか、Modern Standby機でのプロセスサスペンドを抑止する PowerRequestExecutionRequired があります。1413 公式のベストプラクティスは「シナリオ直前にSet、終わったら即Clear、プロセス終了前にハンドルを後始末」です。13
ただしModern Standby機には重要な制限があります。バッテリー駆動のModern Standbyシステムでは、SystemRequired/ExecutionRequired要求はスリープタイムアウト超過の5分後に打ち切られます。また電源方式を問わず、ユーザー操作(電源ボタン・ふた閉じ・スタートメニューのスリープ)によるスリープ突入時には要求は終了します。13 つまり「ノートPCで、ふたを閉じられても、バッテリーでも動き続ける」はアプリの努力では実現できません。そういう要件は電源設定と運用(ふたを閉じてもスリープしない設定、AC給電)で担保するものです。
抑止が実際に効いているかは、管理者権限のコマンドプロンプトで確認できます。
> powercfg /requests
SYSTEM:
[PROCESS] \Device\HarddiskVolume3\Apps\SensorLogger.exe
powercfg /requests はスリープ・画面オフを妨げている電源要求を列挙するコマンドで、「なぜかスリープしないPC」の調査にも、自アプリの抑止確認にも使えます。12 出力の読み方は次のとおりです。
- 出力は要求の種類ごとの見出しで区切られます。
DISPLAY(画面オフの抑止)、SYSTEM(スリープの抑止)、AWAYMODEが基本で、PowerRequestExecutionRequiredに対応するEXECUTIONの区分も出ます。上の例でSYSTEM:の下に自分のアプリが出ていれば、システムのスリープを抑止できている状態です。1213 - 見出しの下に並ぶ行の先頭の
[PROCESS]/[SERVICE]/[DRIVER]が要求元の種別です。この種別と名前は、そのままpowercfg /requestsoverrideに渡す引数になります。12 - 該当する要求がない種類には、要求がない旨の行(英語環境では
None.)が出ます。自アプリの抑止を確認するときは、SYSTEMの下に自分のプロセスが出ているかだけを見れば十分です。 PowerCreateRequest系APIで理由文字列を付けておくと、その文字列がこの一覧に出るため、「どの処理のために抑止しているのか」が運用側にも分かります。1413
逆に、管理者が powercfg /requestsoverride で特定プロセスの要求を無視する設定を入れられることも知っておいてください。12 抑止APIは「お願い」であって絶対ではない、というのが設計の前提です。
最後に行儀の話です。アプリ起動中ずっと ES_CONTINUOUS | ES_SYSTEM_REQUIRED を立てっぱなしにする実装は、ユーザーが設定した電源プランを常駐アプリが恒久的に殺すことを意味します。ノートPCならバッテリーを消耗させ、共有PCなら他の用途にも影響します。抑止は「スリープされては困る処理が実際に走っている区間」だけに絞るのが原則です(公式ドキュメントの例も「録画開始で設定、録画完了でクリア」です10)。なお、ユーザー側の一時的な回避策としてはPowerToys Awakeがあり、これも内部的には同じ仕組み(実行状態を要求するスレッド)で動いています。27 アプリに抑止を実装するか、運用ツールに任せるかの線引きにも使えます。
5. スリープを「前提にする」 ── 検知・再接続・欠測記録
常駐監視や収集アプリの多くは、スリープを禁止するよりもスリープと共存するほうが筋のよい設計になります。必要なのは「入る前に知る」「復帰を知る」「復帰後に立て直す」の3点です。
.NETでは Microsoft.Win32.SystemEvents.PowerModeChanged が入口です。15
using Microsoft.Win32;
SystemEvents.PowerModeChanged += OnPowerModeChanged;
private void OnPowerModeChanged(object sender, PowerModeChangedEventArgs e)
{
switch (e.Mode)
{
case PowerModes.Suspend:
// 猶予は短い: バッファをフラッシュし、計測停止時刻を記録するだけに絞る
_logger.Info("suspend at {0:O}", DateTimeOffset.Now);
_collector.Pause();
break;
case PowerModes.Resume:
// 1) 欠測区間をデータとして残す
_logger.Info("resume at {0:O}", DateTimeOffset.Now);
// 2) 接続は死んでいる前提で破棄して再接続
_connection.Reset();
// 3) 壁時計基準でスケジュールを再計算してタイマーを張り直す
_scheduler.Rebase(DateTimeOffset.Now);
// 4) 立て直しが済んでから収集を明示的に再開する(Pauseしたままにしない)
_collector.Resume();
break;
}
}
このイベントには2つの注意点が公式に明記されています。メッセージポンプが回っていないと発生しないこと(Windowsサービスでは隠しフォーム等が必要)、そしてstaticイベントなので購読解除を怠るとリークすることです。15 GUIアプリなら素直に使えますが、コンソール/サービス型の収集アプリでは、Win32の WM_POWERBROADCAST を受けるメッセージウィンドウを自前で用意するか、HWNDなしでコールバックを受けられる PowerRegisterSuspendResumeNotification を使います(DAM環境での通知もこの経路です)。416
5.1. ウィンドウを持たないアプリで復帰を受け取る
収集ツールやWindowsサービスのようにウィンドウを持たないアプリでは、メッセージウィンドウを自作するより PowerRegisterSuspendResumeNotification のほうが手数が少なくて済みます。この関数は Flags に DEVICE_NOTIFY_CALLBACK を指定し、Recipient にコールバックとコンテキストを収めた DEVICE_NOTIFY_SUBSCRIBE_PARAMETERS へのポインタを渡す形で、成功時は ERROR_SUCCESS(0)を返します。Windows 8 / Windows Server 2012 以降で使えます。1928
using System;
using System.Collections.Generic;
using System.Runtime.InteropServices;
// ウィンドウを持たないコンソール/サービスでサスペンド・復帰を受け取る最小例
// メッセージポンプは不要。Windows 8 / Windows Server 2012 以降
internal sealed class SuspendResumeNotifier : IDisposable
{
private const uint DEVICE_NOTIFY_CALLBACK = 2; // powrprof.h
private const uint PBT_APMSUSPEND = 0x0004; // winuser.h
private const uint PBT_APMRESUMEAUTOMATIC = 0x0012; // winuser.h
private const uint ERROR_SUCCESS = 0;
// ULONG DeviceNotifyCallbackRoutine(PVOID Context, ULONG Type, PVOID Setting)
private delegate uint DeviceNotifyCallbackRoutine(IntPtr context, uint type, IntPtr setting);
[StructLayout(LayoutKind.Sequential)]
private struct DEVICE_NOTIFY_SUBSCRIBE_PARAMETERS
{
public IntPtr Callback;
public IntPtr Context;
}
[DllImport("powrprof.dll")]
private static extern uint PowerRegisterSuspendResumeNotification(
uint flags,
ref DEVICE_NOTIFY_SUBSCRIBE_PARAMETERS recipient,
out IntPtr registrationHandle);
[DllImport("powrprof.dll")]
private static extern uint PowerUnregisterSuspendResumeNotification(IntPtr registrationHandle);
// デリゲートはフィールドで保持する(ローカル変数のままだとGCに回収され、通知時に落ちる)
private readonly DeviceNotifyCallbackRoutine _callback;
private IntPtr _registration;
// 解除に失敗した登録はOS側に残る。そのデリゲートだけはプロセスが終わるまで
// 生かしておく置き場(下の Dispose を参照)
private static readonly List<DeviceNotifyCallbackRoutine> AbandonedCallbacks = new();
public SuspendResumeNotifier()
{
_callback = OnPowerNotification;
var parameters = new DEVICE_NOTIFY_SUBSCRIBE_PARAMETERS
{
Callback = Marshal.GetFunctionPointerForDelegate(_callback),
Context = IntPtr.Zero,
};
uint result = PowerRegisterSuspendResumeNotification(
DEVICE_NOTIFY_CALLBACK, ref parameters, out _registration);
if (result != ERROR_SUCCESS)
{
throw new InvalidOperationException(
$"PowerRegisterSuspendResumeNotification failed with {result}.");
}
}
private uint OnPowerNotification(IntPtr context, uint type, IntPtr setting)
{
// このメソッドはOS(ネイティブ)から直接呼ばれます。managed の例外を
// ここから外へ出すと、受け止める呼び出し元が managed 側に居ないため、
// プロセスごと落ちます。ログの書き込み先が一杯、collector が破棄済み、
// といった「起こりうる」失敗でスリープのたびにアプリが死ぬので、
// 中で完結させ、必ずコードを返します
try
{
switch (type)
{
case PBT_APMSUSPEND:
// 猶予は約2秒。バッファのフラッシュと停止時刻の記録だけに絞る
_logger.Info("suspend at {0:O}", DateTimeOffset.Now);
_collector.Pause();
break;
case PBT_APMRESUMEAUTOMATIC:
// 無人復帰でも必ず届く。重い立て直しはコールバックの外へ逃がす
_logger.Info("resume at {0:O}", DateTimeOffset.Now);
_recovery.RequestRebuild();
break;
}
}
catch (Exception ex)
{
// ログ出力そのものが失敗していることもあるので、ここも守る
try { _logger.Error("power notification failed: {0}", ex); }
catch { /* ここで投げ直したら、上の try の意味が無くなる */ }
}
return ERROR_SUCCESS; // Windowsのエラーコードを返す
}
public void Dispose()
{
if (_registration == IntPtr.Zero)
{
return;
}
uint result = PowerUnregisterSuspendResumeNotification(_registration);
if (result != ERROR_SUCCESS)
{
// 解除できていないのにハンドルを捨てると、登録はOS側に生きたまま
// 追跡できなくなる。そのままこのインスタンスが回収されると _callback も
// 消え、OSは解放済みの関数ポインターを呼ぶ。次のサスペンドで
// プロセスごと落ちるので、デリゲートだけは生かしておく
lock (AbandonedCallbacks)
{
AbandonedCallbacks.Add(_callback);
}
_logger.Error("PowerUnregisterSuspendResumeNotification failed with {0}.", result);
return; // 解除できていない以上、ハンドルは捨てない
}
_registration = IntPtr.Zero;
}
}
実装上の注意は3つです。
- 例外をコールバックの外へ出さないこと。このメソッドはOSから直接呼ばれるので、managed の例外が境界を越えると、受け止める呼び出し元が managed 側に居らずプロセスごと落ちます。ログ出力の失敗や破棄済みオブジェクトへのアクセスといった、実際に起こりうる失敗でスリープのたびにアプリが死ぬことになります。本体を丸ごと
try/catchで囲み、必ずコードを返してください。 - コールバックはシステム側のスレッドから呼ばれます。
PBT_APMSUSPENDの猶予が約2秒なのはウィンドウ経由の場合と同じなので、ここで再接続やネットワーク越しの後始末をしてはいけません。1718 復帰処理も、フラグを立てて別スレッドに任せるほうが安全です。 - 登録解除を忘れないこと。
PowerUnregisterSuspendResumeNotificationに登録ハンドルを渡します。29 戻り値も見てください。解除できていないのに登録ハンドルを捨てると、OS側には登録が残ったまま追跡手段だけが失われます。その状態でインスタンスが回収されるとコールバックのデリゲートも消え、OSは解放済みの関数ポインターを呼ぶことになります。 - ウィンドウハンドルで受け取りたい場合は、user32.dll の
RegisterSuspendResumeNotificationにDEVICE_NOTIFY_WINDOW_HANDLEを渡す方法もあります(こちらは通常どおりWM_POWERBROADCASTが飛んできます)。30
Windowsサービスとして作る場合は、ServiceBase の CanHandlePowerEvent を true にして OnPowerEvent(PowerBroadcastStatus) をオーバーライドする手もあります。サービス制御マネージャー経由で同じ電源イベントが届くため、P/Invokeなしで書けます。31
5.2. Win32レベルのイベントの意味
ウィンドウ経由でもコールバック経由でも、届くイベントの意味は共通です。ここは押さえておきましょう。
- PBT_APMSUSPEND: スリープ直前の通知。1アプリあたり約2秒しか猶予がなく、超過するとシステムに中断されることがあります。ここでやるのはフラッシュと時刻記録程度に絞り、ネットワーク越しの後始末のような時間のかかる処理を書いてはいけません。1718
- PBT_APMRESUMEAUTOMATIC: 復帰のたびに必ず届く通知。再接続・再スケジュールはここに書きます。32
- PBT_APMRESUMESUSPEND: ユーザーの操作で(またはユーザーが戻ってきて)復帰した場合に、PBT_APMRESUMEAUTOMATICの後に届きます。リモートウェイク等の自動復帰では届かないため、「復帰処理」をこちらだけに書くと無人復帰時に立て直しが走りません。33
- さらに、バッテリー切迫などのクリティカルなスリープ突入では事前通知そのものが来ません。18 Suspendイベントを受け取れなかったケースでも復帰処理が成立するよう、Resume側を冪等に書いておきます。
5.3. 欠測を欠測として記録する
もう1つ、復帰対応と同じくらい大事なのが欠測を欠測として記録することです。スリープをまたいだ収集データは「値がない」のではなく「システムが止まっていたので測っていない」のであり、その区間をsuspend/resumeの時刻つきでログとデータの両方に残しておくと、後からグラフの空白を見た人が障害と混同しなくなります。長期稼働アプリのログに何を残すべきかという観点は「産業用カメラ長期稼働クラッシュ調査 - ハンドルリーク編」でも扱っています。
なお、スリープと似た「気づいたら遅くなっていた」系の問題として、Windows 11の効率モード(EcoQoS)による抑制もあります。こちらは「Windows効率モードとは - 緑の葉アイコンとオフにする方法」を参照してください。
6. 決まった時刻に確実に動かす ── タスクスケジューラの起床実行
「深夜2時に集計して転送する」のような時刻起点の処理を、常駐アプリのタイマー+スリープ抑止で実現するのは筋がよくありません。夜通しスリープを殺すことになるからです。この用途はタスクスケジューラの「タスクの実行時にスリープを解除する」(WakeToRun)が本命です。
WakeToRunを有効にしたタスクは、実行時刻にスリープ・休止からコンピューターを起床させ、タスクが完了するまでシステムを起きたままにします(すでに起きていた場合も完了まで維持を要求します)。起床時に画面は消えたままのことがありますが、それで正常です。20
ただし、起床が成立するには条件があります。公式のトラブルシューティングにも挙げられているとおり、電源オプションの「スリープ解除タイマーの許可」(Allow Wake Timer)が有効であること、BIOS側のウェイク設定が有効であることが前提で、近年のノートPCは省電力設計上ウェイクを許可しない構成も珍しくありません。21 powercfg /waketimers で現在有効なウェイクタイマーを列挙できるので12、導入先の実機で「本当に起きるか」を必ず検証してください。自前アプリで起床させたい場合は、SetWaitableTimer の fResume にTRUEを渡すウェイク可能タイマーという手段もあります。このときシステムは自動起床後、無人アイドルタイマー(最低2分)の間だけ起きており、アプリが SetThreadExecutionState で「使用中」を宣言しなければ速やかに再スリープします。起床後の処理が長い場合は第4章の抑止と組み合わせる、というのが正しい合わせ技です。23
タスク自体の実行アカウント・0x1で終わる問題・多重起動防止といった運用設計は「タスクスケジューラのタスクが実行されない・0x1で終わる ── 原因の切り分けと安全な運用設計」にまとめています。
7. 判断表 ── 抑止か、復帰対応か、起床か
3つの設計は排他ではなく、アプリ種別ごとに主・従を決めて組み合わせるものです。
| アプリ種別 | 第一候補 | 組み合わせ・補足 |
|---|---|---|
| 計測・データ収集(数時間〜数日の連続測定) | 測定区間だけ ES_SYSTEM_REQUIRED で抑止 |
復帰対応も必ず実装(ユーザー操作のスリープは防げない10)。バッテリー駆動のModern Standby機は5分打ち切りがあるためAC給電を要件に13 |
| 夜間バッチ・定時転送 | タスクスケジューラ + WakeToRun20 | 常駐+抑止より省電力で確実。ウェイクタイマー許可の確認が前提21 |
| 常駐監視・通知エージェント | スリープ前提(PowerModeChangedで検知→再接続・再スケジュール・欠測記録)15 | 監視が主目的の専用機なら、アプリでなく電源プラン側でスリープを無効化する |
| デスクトップ操作ツール(プレゼン・ダッシュボード表示等) | 表示中だけ ES_DISPLAY_REQUIRED を ES_SYSTEM_REQUIRED と併用10 |
表示終了で必ずクリア。常時表示端末なら電源設定で対応 |
| 24/7の装置制御・ラインPC | 電源設定でスリープ自体を無効化(運用で担保) | アプリの抑止APIを保険にしない。powercfg /requests を定期点検に使う12 |
判断の軸は2つです。「止まってよい時間帯があるか」(あるならタスクスケジューラ、ないなら電源設定)と、「誰のPCで動くか」(ユーザーの私物・共用ノートで動くアプリほど、抑止ではなく復帰対応に寄せる)です。
7.1. 「電源設定でスリープ自体を無効化」の中身
表の中で何度か出てきた「電源設定で無効化」は、具体的には次の操作です。装置制御PCのようにアプリではなく運用でスリープを止める場合は、ここまでやって初めて担保されます。
| やること | 場所・コマンド |
|---|---|
| スリープまでの時間を「なし」にする | 設定 > システム > 電源(ノートPCでは「電源とバッテリー」) > 画面とスリープ。または コントロールパネル > ハードウェアとサウンド > 電源オプション > プラン設定の変更。コマンドなら powercfg /change standby-timeout-ac 0(0はスリープしないの意味)1234 |
| 画面オフまでの時間も見直す | 同じ画面の「画面の電源を切る」。コマンドなら powercfg /change monitor-timeout-ac 012 |
| ふた閉じ・電源ボタンの動作を変える | 電源オプション > 電源ボタンの動作を選択する > 「何もしない」を選ぶ。SetThreadExecutionState では防げないのがこの経路10 |
| 機種の方式を確認する | powercfg /a。S3機かModern Standby機かで次の注意点が変わる3 |
Modern Standby機ではもう一段の注意が必要です。Modern Standby対応機は、画面がオフになったときにスタンバイに入ります。公式ドキュメントは、その契機として電源ボタン・ふた閉じ・スタートメニューのスリープに加えて「システムがアイドルアウトしたとき」を挙げています。35 つまりS3機の感覚で「スリープのタイムアウトだけ切っておけば大丈夫」と考えると、画面オフからスタンバイに入る経路が残ります。スリープと画面オフの両方のタイムアウトを見直し、powercfg /a で方式を確認したうえで、実機で一晩放置して止まらないことを確認するまでを手順に入れてください。
バッテリー駆動時の扱いも忘れがちです。standby-timeout-ac はAC給電時の設定で、バッテリー時は standby-timeout-dc が使われます。装置用PCがノートPCなら、AC給電を前提条件として明記しておくのが安全です(4章のとおり、バッテリー駆動のModern Standby機では電源要求そのものが5分で打ち切られます)。13
8. まとめ
- スリープにはS3・休止(S4)・Modern Standby(S0 low power idle)があり、
powercfg /aで判別できます。Modern Standby機でもデスクトップアプリはDAMでサスペンドされるため、「スリープ中も動く」前提は成立しません。 - スリープ中はスレッドもタイマーも進みません。Windows 8以降、相対タイマー・待機はスリープ時間を数えず残り時間を持ち越し、.NETのタイマーも .NET 11 から同じ挙動になります(.NET 10までは復帰直後に発火し得ます)。経過時間APIはスリープ時間を含む/含まないが混在します。経過はStopwatch、壁時計はDateTimeで持ち、復帰時に基準を取り直してください。
- TCP接続は中間機器のアイドルタイムアウトで黙って死にます。復帰イベントで破棄・再接続が定石です。
- 抑止は
SetThreadExecutionState(ES_CONTINUOUS | ES_SYSTEM_REQUIRED)を必要区間だけ。ユーザー操作のスリープは防げず、Modern Standby機のバッテリー駆動では5分で打ち切られます。確認はpowercfg /requests。 - 復帰対応は
SystemEvents.PowerModeChanged/WM_POWERBROADCASTで。サスペンド通知の猶予は約2秒、通知なしのスリープもあるため、Resume側を冪等に書き、欠測区間を記録します。 - 定時実行はタスクスケジューラのWakeToRunが本命。ウェイクタイマーの電源設定と実機での起床確認まで含めて設計してください。
関連記事
- タスクスケジューラのタスクが実行されない・0x1で終わる ── 原因の切り分けと安全な運用設計
- WindowsでSleep(1)よりイベント待機を優先すべき理由
- TCP再送で産業用カメラ通信が止まる原因と切り分け
- 産業用カメラ長期稼働クラッシュ調査 - ハンドルリーク編
- Windows効率モードとは - 緑の葉アイコンとオフにする方法
関連する相談領域
合同会社小村ソフトでは、計測・監視・データ収集など長時間稼働するWindowsアプリの設計と実装、「夜中に止まっていた」「記録が歯抜けになる」といった長期稼働特有の不具合の調査を扱っています。スリープ・電源管理が絡む再現しづらい症状の切り分けもご相談ください。
参考リンク
-
Microsoft Learn, System Sleeping States. S1〜S4がスリープ状態であり計算タスクを実行しないこと、S3はメモリのみ保持・S4は休止ファイルへ保存すること、powercfg /a で利用可能なスリープ状態を列挙できることについて。 ↩ ↩2 ↩3 ↩4 ↩5
-
Microsoft Learn, System power states. S0 low power idle(Modern Standby)では低電力のままシステムが部分的に動作し続けること、Modern Standby対応SoCシステムはS1〜S3を使わないこと、クリティカルな遷移では通知が行われないことについて。 ↩ ↩2 ↩3
-
Microsoft Learn, Overview of Modern Standby Testing and Diagnostics. powercfg /a でModern Standby対応(Standby (S0 Low Power Idle) の表示)を判別できることについて。 ↩ ↩2 ↩3
-
Microsoft Learn, Desktop Activity Moderator. DAMがデスクトップアプリの実行をS3同等に抑え込むこと、対話セッションのプロセスは全スレッドサスペンド・セッション0はスロットリングされること、サスペンド前のWM_POWERBROADCAST通知、タイマー挙動や稼働時間と壁時計の不整合、接続寿命への考慮が必要なことについて。 ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8
-
Microsoft Learn, SetWaitableTimerEx function. 相対時間指定のタイマーが、Windows 7以前は低電力状態の時間を含んで(スリープ中もカウントダウンが進んで)いたのに対し、Windows 8以降は含まない(スリープ中はカウントダウンが進まない)ことについて。 ↩ ↩2 ↩3
-
Microsoft Learn, Environment.TickCount made consistent with Windows timeout behavior. Environment.TickCount/TickCount64が.NET 10まではGetTickCount64ベース(スリープ時間を含む)で、.NET 11からQueryUnbiasedInterruptTimeベース(含まない)に変更される破壊的変更、Windows 8以降でタイムアウトを指定する待機API(SleepEx/WaitForMultipleObjectsEx)が非稼働時間をカウントしなくなっていたこと、この変更により復帰直後にタイマーが発火しなくなるコードがあり得ることについて。 ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9
-
Microsoft Learn, Windows Time. GetTickCount/GetTickCount64の経過時間がスリープ・休止の時間を含むこと、QueryUnbiasedInterruptTimeはworking stateの時間のみを含むことについて。 ↩ ↩2 ↩3
-
Microsoft Learn, Acquiring high-resolution time stamps. マネージドコードのSystem.Diagnostics.StopwatchがQPCを時刻基盤とすること、QueryPerformanceCounterがstandby・hibernate・connected standby中の時間を含むティック数を返すことについて。 ↩ ↩2
-
Microsoft Learn, Load Balancer TCP Reset and Idle Timeout. ロードバランサーの既定動作がアイドルタイムアウト(既定4分)到達時にフローをサイレントにドロップすることであり、TCPリセット送信は明示的に有効化する機能であること、対策としてTCP keep-aliveが用いられることについて。 ↩ ↩2
-
Microsoft Learn, SetThreadExecutionState function. ES_CONTINUOUS/ES_SYSTEM_REQUIRED/ES_DISPLAY_REQUIREDの意味、ES_CONTINUOUSなしではアイドルタイマーのリセットのみであること、ユーザーによるスリープ操作は防げないこと、必要な処理の間だけ設定し完了後にクリアする使用例について。 ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9
-
Microsoft Learn, System Sleep Criteria. システムがSetThreadExecutionStateを呼んだアプリ/スレッドをカウントし、カウントがゼロでユーザー入力がなければスリープに入ることについて。 ↩ ↩2
-
Microsoft Learn, Powercfg command-line options. /requests がスリープや画面オフを妨げる電源要求を列挙すること、/requestsoverride で特定プロセスの要求を無視できること、/waketimers で有効なウェイクタイマーを列挙できることについて。 ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8 ↩9
-
Microsoft Learn, PowerSetRequest function. PowerRequestSystemRequired/PowerRequestExecutionRequired等の要求種別、Modern StandbyのDC電源では要求がスリープタイムアウト超過の5分後に打ち切られること、ユーザー起因のスリープ突入で要求が終了すること、理由文字列を付け直前にSet・直後にClearするベストプラクティスについて。 ↩ ↩2 ↩3 ↩4 ↩5 ↩6 ↩7 ↩8
-
Microsoft Learn, PowerCreateRequest function. REASON_CONTEXTを指定して電源要求オブジェクトを作成し、不要になったらCloseHandleで解放することについて。 ↩ ↩2 ↩3
-
Microsoft Learn, SystemEvents.PowerModeChanged Event. サスペンド/レジューム時に発生するイベントであること、メッセージポンプが動いていないと発生しないこと(サービスでは隠しフォーム等が必要)、staticイベントのため購読解除しないとリークすることについて。 ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, WM_POWERBROADCAST message. 電源管理イベントがWM_POWERBROADCASTメッセージ(PBT_APMSUSPEND/PBT_APMRESUMEAUTOMATIC/PBT_APMRESUMESUSPEND等)としてウィンドウに通知されることについて。 ↩ ↩2
-
Microsoft Learn, PBT_APMSUSPEND event. サスペンド直前に通知されること、処理の猶予が約2秒であり超過するとシステムに中断されうることについて。 ↩ ↩2 ↩3
-
Microsoft Learn, System Power Management Events. クリティカルバッテリー等による緊急サスペンドでは事前通知が行われないこと、サスペンド通知の処理はアプリごとに最大2秒でタイムアウトすることについて。 ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, PowerRegisterSuspendResumeNotification function. Flagsに DEVICE_NOTIFY_CALLBACK を指定し、Recipient に DEVICE_NOTIFY_SUBSCRIBE_PARAMETERS へのポインタを渡すこと、コールバックの Type に PBT_APMSUSPEND / PBT_APMRESUMESUSPEND / PBT_APMRESUMEAUTOMATIC が入ること、成功時に ERROR_SUCCESS を返すこと、Windows 8 / Windows Server 2012 以降で Powrprof.dll に含まれることについて。 ↩ ↩2
-
Microsoft Learn, ITaskSettings::get_WakeToRun method. WakeToRunがタスク実行時にコンピューターを起床させ、タスク完了まで起きたままにすること、起床時に画面が消えたままの場合があることについて。 ↩ ↩2 ↩3
-
Microsoft Learn, Automatic maintenance. 定時起床が動作しない場合の確認項目として、BIOSのウェイク設定、電源オプションの「Allow Wake Timer」、タスクのWakeToRun設定が挙げられていること、近年のノートPCではS3起床を許可しない構成が一般的であることについて。 ↩ ↩2 ↩3
-
Microsoft Learn, What is Modern Standby. Modern StandbyがS0低電力アイドルモデルでネットワーク接続を維持したまま瞬時のオン/オフを実現すること、復帰(電源ボタンから画面点灯)が1秒未満であることについて。 ↩
-
Microsoft Learn, System Wake-up Events. SetWaitableTimerのfResumeをTRUEにするとタイマーでシステムを起床できること、自動起床後は最低2分の無人アイドルタイマーが設定され、SetThreadExecutionStateで使用中を示さなければ再スリープすることについて。 ↩ ↩2 ↩3
-
Microsoft, .NET and .NET Core Support Policy. .NETのメジャーリリースが毎年11月であること、.NET 10 が2025年11月11日にリリースされたLTS版であることについて。 ↩
-
Microsoft Learn, QueryUnbiasedInterruptTime function. unbiased interrupt timeがworking stateの時間のみを数え、スリープ・休止の時間を含まないことについて。 ↩
-
Microsoft Learn, Modern standby network connectivity. Adaptive Connected Standbyにより、バッテリー駆動時は必要とするシナリオがない限りスリープ中のネットワーク活動が静止されることについて。 ↩
-
Microsoft Learn, PowerToys Awake utility. 電源プランを変更せずにPCを起きたままにするユーティリティであり、マシン状態を要求するバックグラウンドスレッドを生成する仕組みで動作すること、終了すると通常の電源プラン動作に戻ることについて。 ↩
-
Microsoft Learn, DEVICE_NOTIFY_SUBSCRIBE_PARAMETERS structure. 構造体が Callback と Context の2メンバーを持つこと、コールバック DeviceNotifyCallbackRoutine が ULONG(PVOID Context, ULONG Type, PVOID Setting) の形でWindowsのエラーコードを返すことについて。 ↩
-
Microsoft Learn, PowerUnregisterSuspendResumeNotification function. 登録ハンドルを渡して通知の登録を解除すること、成功時に ERROR_SUCCESS を返すことについて。 ↩
-
Microsoft Learn, RegisterSuspendResumeNotification function. Flags に DEVICE_NOTIFY_WINDOW_HANDLE を指定するとウィンドウにイベントが配信され、DEVICE_NOTIFY_CALLBACK を指定するとコールバックで受け取れることについて。 ↩
-
Microsoft Learn, ServiceBase.OnPowerEvent(PowerBroadcastStatus) Method. Windowsサービスがコンピューターの電源状態の変化を受け取るメソッドであり、CanHandlePowerEvent プロパティが true のときにオーバーライドすることが前提であることについて。 ↩
-
Microsoft Learn, PBT_APMRESUMEAUTOMATIC event. 復帰のたびに必ず配信されるイベントであり、ユーザーの存在を示すものではないこと、ユーザー活動を検出するとその後にPBT_APMRESUMESUSPENDが配信されることについて。 ↩
-
Microsoft Learn, PBT_APMRESUMESUSPEND event. ユーザー起因・ユーザー検出時の復帰でPBT_APMRESUMEAUTOMATICの後に配信されること、リモートウェイクではPBT_APMRESUMEAUTOMATICのみが配信されることについて。 ↩
-
Microsoft Learn, Sleep idle timeout. 自動的にスリープに入るまでの無操作時間を指定する設定であり、最小値の0が「スリープに入らない」を意味することについて。 ↩
-
Microsoft Learn, Prepare software for modern standby. Modern Standbyに入るのは画面がオフになったときであり、その契機が電源ボタン・ふた閉じ・設定からのスリープ選択・システムのアイドルアウトであること、DAMフェーズ以降はデスクトップアプリの実行が止められ、セッション0のサービスはスロットリングされること、電源要求がAC給電では無期限に、DC給電では最大5分このフェーズを引き延ばすことについて。 ↩
関連する記事
同じタグを共有する最新の記事です。さらに近い話題で知識を深められます。
MAX_PATHとWindowsのパス・ファイル名の落とし穴 ── 260文字制限、予約名、末尾ドット、大文字小文字
「ファイルが見つかりません」の定番原因であるパス・ファイル名の制限を整理します。MAX_PATH=260文字の内訳、LongPathsEnabledによる長パス有効化、CON等の予約名、末尾ドットの正規化、Path.Combineの罠まで解説します。
ネットワークドライブとUNCパスの落とし穴 ── 業務アプリでファイルサーバー(共有フォルダ)を扱う実務
業務アプリから共有フォルダへ出力・監視するときの定番トラブルを整理します。ドライブ文字(Z:)がサービスから見えない理由、実行アカウントごとに必要な権限、エラー1219、FileSystemWatcherの注意点まで解説します。
Windowsアプリのタスクトレイ常駐とトースト通知 ── NotifyIconの落とし穴とAppNotificationの選び方
業務Windowsアプリのタスクトレイ常駐とトースト通知の実装を整理します。NotifyIconの正しい使い方、Explorer再起動時の再登録、トーストAPI3種の選定判断表、通知が届かないケースまで解説します。
自社開発のWindowsアプリがウイルス扱いされたら ── Microsoft Defenderの誤検知対応と、性能影響との付き合い方
自社開発のWindowsアプリがMicrosoft Defenderに誤検知されたときの正規の対処を整理します。現代のウイルス対策の仕組み、Microsoftへの誤検知報告、隔離からの復元、除外設定の正しい入れ方とリスクまで解説します。
Arm版Windowsで業務アプリは動くのか ── x64エミュレーション(Prism)とネイティブDLL・COMの現実
「Arm版Windowsで業務アプリは動くのか」に開発者・情シス向けに答えます。x64エミュレーション(Prism)の仕組み、ドライバーなど動かない層、.NETのAnyCPUとP/Invokeの問題、Arm対応チェックリストまで整理します。
関連トピック
このテーマと近いトピックページです。記事を起点に、関連するサービスや他の記事へ進めます。
Windows技術トピック
Windows 開発、不具合調査、既存資産活用の技術トピックをまとめた入口です。
UI スレッド / タイマーテーマ
WPF / WinForms、UI スレッド、async/await、タイマー設計を整理するトピックです。
不具合調査 / 長期稼働テーマ
再現しにくい不具合、通信停止、長期稼働障害、失敗パス検証を整理するトピックです。
このテーマがつながるサービス
この記事は次のサービスページにつながります。近い入口からご覧ください。
Windowsアプリ開発
業務アプリ、装置連携、通信ツールなどの Windows ソフト開発を支援します。
不具合調査・原因解析
再現しにくい障害、長期稼働後の不具合、通信停止などの調査を支援します。
よくある質問
この記事のテーマについて、相談時によくある質問をまとめています。
- アプリ実行中だけPCをスリープさせないようにするには?
- SetThreadExecutionState(ES_CONTINUOUS | ES_SYSTEM_REQUIRED) を処理の開始時に呼び、終了時に SetThreadExecutionState(ES_CONTINUOUS) でクリアするのが基本です。画面も点けたままにしたい場合だけ ES_DISPLAY_REQUIRED を足します。効くのは無操作タイムアウトによる自動スリープだけで、ユーザーが電源ボタンやふたを閉じて指示したスリープは防げません。抑止が効いているかは powercfg /requests で確認できます。
- Modern Standbyとは何ですか? 従来のスリープと何が違いますか?
- S0 low power idleとも呼ばれる新しいスリープ方式で、システムは低電力ながら部分的に動作し続け、瞬時に復帰できるのが特徴です。Modern Standby対応機は従来のS3スリープをサポートしません。ただし「動き続ける」のはOSが許可した活動だけで、デスクトップアプリはDAM(Desktop Activity Moderator)によりスレッドごとサスペンドされるため、アプリ視点ではS3と同様に止まります。自分のPCがどちらの方式かは powercfg /a で確認できます。
- スリープ復帰後にTCP接続が使えなくなるのはなぜですか?
- スリープ中はアプリが通信できないため接続がアイドル状態になり、経路上のNAT・ファイアウォール・ロードバランサーなどの中間機器がアイドルタイムアウトでフローを破棄するためです。多くの機器は破棄を通知せず黙って落とすので、アプリ側のソケットは一見正常なまま、復帰後の最初の送受信で初めてエラーになるか、タイムアウトまで固まります。復帰イベントを受けたら接続を疑って作り直すのが定石です。
- 夜間バッチを確実に動かすにはスリープ抑止とタスクスケジューラのどちらがよいですか?
- タスクスケジューラの「タスクの実行時にスリープを解除する」(WakeToRun)が第一候補です。実行時刻にPCを起床させ、タスク完了まで起きたままにしてくれるため、夜通しスリープを殺しておく必要がありません。ただしウェイクタイマーが電源オプションで無効だと起床しないので、「スリープ解除タイマーの許可」の設定と実機での起床確認が必須です。常時スリープ抑止は、その間の電力を無駄にするうえユーザーの電源設定を上書きする行儀の悪い設計になります。