「ノートPCを閉じて、翌朝開いたら業務アプリがエラーだらけになっていた」「装置の監視アプリが、昼休みの後だけデータを取りこぼす」「Excelに出力する常駐ツールが、たまに接続エラーで止まっている」── これらの問い合わせには、共通するひとつの容疑者がいます。スリープです。
デスクトップPCが主流だった時代の業務アプリは、「PCは点けっぱなし」を暗黙の前提に書かれてきました。しかし現在の主戦場はノートPCで、既定では放置すれば数分でスリープに入ります。しかもModern Standby(モダンスタンバイ)対応機では、スリープの意味論そのものが従来と変わっています。この記事では、Windowsで業務アプリ・装置制御ソフトを作る開発者を対象に、スリープ前後にOSがアプリへ何を通知するのか、何が壊れるのか、復帰に強いアプリをどう書くのかを、一次情報にもとづいて整理します。
1. まず結論
- スリープはアプリに「拒否権のない」イベントです。直前に
WM_POWERBROADCAST(PBT_APMSUSPEND)で通知されますが、猶予はおよそ2秒で、緊急サスペンドでは通知すら来ません。12 - サスペンドからの復帰では PBT_APMRESUMEAUTOMATIC が届き、ユーザー操作による復帰ではさらに PBT_APMRESUMESUSPEND が届きます。再接続などの必須処理は前者に置くのが基本です。ただしModern Standbyの低電力アイドルの出入りはこの通知と一致しない場合があるため、通知は補助と割り切ります。34
- TCP接続・シリアルポート・デバイスハンドルは復帰をまたいで生き残らない前提で設計します。復帰通知と通信エラーを契機に張り直す再接続ロジックが本体です。
- タイマーと時刻の扱いに注意。スリープ中は周期処理が止まり、復帰直後の発火のされ方はタイマーAPIや実行環境ごとに異なります。「経過時間の巨大なジャンプ」も起きるため、復帰時にスケジュールを組み直すのが安全です。
- スリープさせたくない区間は明示的に抑止します。
SetThreadExecutionState(ES_SYSTEM_REQUIRED)や電源要求(PowerSetRequest)を使い、処理が終わったら必ず解除します。45 - Modern Standby機ではスリープ中もシステムは間欠的に動きますが、デスクトップアプリは一時停止されます。「うちのアプリはスリープ中も動くはず」という期待は持てません。6
- 調査は
powercfg(/requests、/lastwake、/sleepstudy)とイベントログのKernel-Powerが定番です。
2. スリープ前後に何が起きるか ── 電源イベントの流れ
OSは電源状態の変化を、すべてのアプリに WM_POWERBROADCAST メッセージでブロードキャストします。2 スリープと復帰に関わる主要イベントは3つです。
| イベント | 意味 |
|---|---|
| PBT_APMSUSPEND | まもなくスリープに入る(最後の準備の機会) |
| PBT_APMRESUMEAUTOMATIC | 復帰した(復帰時に必ず届く) |
| PBT_APMRESUMESUSPEND | ユーザー操作による復帰(こちらは条件付き) |
PBT_APMSUSPEND はスリープ直前の通知で、ここでファイルを閉じ、状態を保存する準備ができます。ただし条件が2つあります。第一に、処理に許される時間はアプリごとにおよそ2秒で、超えるとシステムは待たずに進みます。1 第二に、バッテリー残量が危機的な場合などの緊急サスペンドでは、この事前通知なしでいきなりスリープします。2 「スリープ前に必ず完了させる」設計は成立しません。通知は「間に合えばやる」機会と捉え、本体は復帰側に置きます。
復帰側は2段階です。PBT_APMRESUMEAUTOMATIC はサスペンド遷移からの復帰時に届きます。そのうえで、電源ボタンやキー入力などユーザーの操作で復帰した(またはその後ユーザーの在席が検知された)場合には、続けて PBT_APMRESUMESUSPEND が届きます。逆に、ネットワーク経由の遠隔起動やメンテナンスのための無人復帰では PBT_APMRESUMEAUTOMATIC しか届きません。3 この2段階は使い分けのヒントそのものです ── 接続の張り直しなど機械的な立て直しは PBT_APMRESUMEAUTOMATIC で、画面の更新や再ログイン要求などユーザー向けの動作は PBT_APMRESUMESUSPEND で行います。
sequenceDiagram
accTitle: スリープと復帰の通知の流れ
accDescr: スリープ直前にPBT_APMSUSPENDが約2秒の猶予付きで届き、復帰時はPBT_APMRESUMEAUTOMATICが必ず届いて、ユーザー操作による復帰の場合だけ続けてPBT_APMRESUMESUSPENDが届く
participant OS as OS
participant A as アプリ
OS->>A: PBT_APMSUSPEND(猶予約2秒)
A->>A: 状態保存・接続クローズ
Note over OS: スリープ(コードは動かない)
OS->>A: PBT_APMRESUMEAUTOMATIC(復帰時に届く)
A->>A: 再接続・状態の立て直し
OS->>A: PBT_APMRESUMESUSPEND(ユーザー復帰時のみ)
A->>A: 画面更新などユーザー向け処理
図1: 通知は「直前に一言、復帰後に一言か二言」だけ。立て直しの主役は復帰側の処理になる。
flowchart TB
accTitle: 通常スリープと緊急サスペンドの違い
accDescr: 通常のスリープでは直前にPBT_APMSUSPENDが届き約2秒の準備時間があるが、クリティカルバッテリーなどによる緊急サスペンドでは事前通知なしで停止するため、事前通知に依存した設計は成立しない
n2["通常のスリープ"] --> pre["PBT_APMSUSPEND(約2秒の猶予)"]
pre --> s1["準備してから停止"]
e2["緊急サスペンド(電池切れ間際)"] --> s2["事前通知なしで停止"]
s2 -.-> l2["「通知が来る前提」の設計は不成立"]
図2: 緊急サスペンドは予告なし。だから準備は「できたら儲けもの」で、本体は復帰側に置く。
なお WM_POWERBROADCAST は低電力状態の種類(スリープか休止か)を区別しません。4 アプリにとっては「止まって、戻ってきた」という1種類の事象として扱うのが正しい抽象です。ウィンドウを持たないサービスやコンソールアプリでは、RegisterSuspendResumeNotification をコールバック方式(DEVICE_NOTIFY_CALLBACK)で使って同じ通知を受け取れます。7
flowchart TB
accTitle: 復帰2段階の処理の使い分け
accDescr: 復帰時に届くPBT_APMRESUMEAUTOMATICには再接続など機械的な立て直しを置き、ユーザー操作時だけ届くPBT_APMRESUMESUSPENDには画面更新や再ログイン要求などユーザー向けの処理を置く
ra["PBT_APMRESUMEAUTOMATIC(復帰時)"] --> m["機械的な立て直し"]
rs["PBT_APMRESUMESUSPEND(ユーザー復帰時)"] --> u["ユーザー向けの処理"]
m -.-> m1["再接続・ハンドル開き直し"]
u -.-> u1["画面更新・再ログイン要求"]
図3: 無人復帰では後者が来ないため、必須の立て直しを後者に置くと取りこぼす。
3. Modern Standby ── 「スリープ」の意味が変わった
もうひとつ押さえるべき現代的事情が Modern Standby です。従来のS3スリープが「システム全体を止める」単純なモデルだったのに対し、Modern Standby機のスリープは、画面を消した後もシステムが間欠的に動き続ける、スマートフォンに近いモデルです。
ここで業務アプリにとって重要なのは、デスクトップアプリはスリープ入りの最初の段階で Desktop Activity Moderator(DAM)により一時停止されることです。6 システム自体はネットワークの維持や通知の受信のために時々動きますが、その恩恵を受けるのはこの仕組みに対応したコンポーネントであって、通常のデスクトップアプリのコードは走りません。つまり開発者の視点では、Modern StandbyでもS3でも結論は同じ ── スリープ中に自分のコードは動かない前提で設計する、です。
flowchart TB
accTitle: 従来のスリープとModern Standbyの違い
accDescr: 従来のS3スリープはシステム全体が停止するが、Modern Standbyでは画面消灯後もシステムが間欠的に動く。ただしデスクトップアプリはDAMにより一時停止されるため、どちらでもアプリのコードは動かない
s3["従来のS3スリープ:全体が停止"] --> conc["アプリのコードは動かない"]
ms["Modern Standby:システムは間欠動作"] --> dam["デスクトップアプリはDAMで一時停止"]
dam --> conc
図4: モデルは変わっても、デスクトップアプリにとっての結論は「スリープ中は動けない」で共通。
もうひとつの注意は通知の当てにならなさです。Modern Standbyでは低電力アイドルの出入りが従来のサスペンド遷移と一致せず、通知が飛んでこないまま接続が壊れていることがありえます。復帰通知は補助と割り切り、エラー検知からの再接続(5章)を回復の本線に置いてください。
もうひとつの違いは挙動の「ぬるっと感」です。スリープの深部に入るまでが段階的で、切断や停止のタイミングがS3ほど明確ではありません。「画面が消えただけ」と「スリープした」の区別がユーザーにも付きにくいため、症状の聞き取りでは「フタを閉じたか」「何分放置したか」を確認する必要があります。
4. 何が壊れるのか ── 定番の症状
TCP接続が死んでいる。スリープ中、接続相手・NAT・ファイアウォールはこちらの沈黙をタイムアウトとして処理し、接続を破棄します。悪いことに、こちら側のソケットはエラーを知らないままなので、復帰後に送受信して初めて失敗します。あるいはもっと悪く、受信待ちのままいつまでもエラーにならないこともあります(だからキープアライブが要るのです)。データベース接続やWebSocketも同じ構図です。
シリアルポート・USBデバイスのハンドルが無効になる。USB接続の機器は復帰時に一度「抜けて挿し直された」ように見えることがあり、開いていたハンドルはエラーを返すようになります。装置制御アプリで「昼休みの後だけ通信エラー」となる典型パターンです。再接続設計はシリアル通信の記事でも扱っています。
時間の連続性が壊れる。「10秒ごとにポーリング」のようなタイマー駆動処理は、スリープ中は発火しません。復帰直後にどう発火するか(期限切れ分が直ちに1回発火する、次の周期まで何も起きない等)は使っているタイマーAPIや実行環境によって異なるため、打ち逃した分の扱いを暗黙の挙動に任せず、復帰通知でスケジュールを組み直すのが安全です。また、経過時間ベースの計算(前回時刻との差分)が突然「8時間ぶん」になって、平均値の計算やタイムアウト判定が壊れる。「毎晩2時に実行」のような定時処理は、その時刻にPCが眠っていれば単に実行されません(必要ならタスクスケジューラのスリープ解除機能で起こします)。
flowchart TB
accTitle: 時間の連続性が壊れる3つの形
accDescr: 周期処理はスリープ中に止まり復帰後の発火はAPIごとに異なるため復帰時にスケジュールを組み直し、前回時刻との差分は復帰後に巨大な値になるためガードし、定時処理は眠っていると実行されないためタスクスケジューラのスリープ解除を検討する
t1["周期処理:スリープ中は止まる"] -.-> g1["復帰時にスケジュールを組み直す"]
t2["経過時間の差分:巨大化"] -.-> g2["異常な差分をガードする"]
t3["定時処理:眠っていて未実行"] -.-> g3["スリープ解除の設定で起こす"]
図5: タイマーと時刻の処理は「時間が飛ぶ」前提で書く。3つの形それぞれに対処の型がある。
flowchart TB
accTitle: スリープをまたいで壊れる3つのもの
accDescr: スリープをまたぐと、TCP接続は相手側のタイムアウトで破棄され、USB機器のハンドルは再接続扱いで無効になり、経過時間ベースの処理は巨大な時間ジャンプを観測する。それぞれ再接続・開き直し・差分ガードで立て直す
sleep["スリープ区間"] --> tcp["TCP接続:相手側で破棄済み"]
sleep --> usb["USB機器:ハンドルが無効化"]
sleep --> time["経過時間:巨大なジャンプ"]
tcp -.-> r1["エラー検知と再接続"]
usb -.-> r2["デバイスを開き直す"]
time -.-> r3["異常な差分をガード"]
図6: 壊れるものは「接続」「ハンドル」「時間の連続性」の3系統で、それぞれ立て直しの型が決まっている。
共有リソースへの再認証。ネットワークドライブやVPNは復帰後に再確立が必要なことがあり、復帰直後の数秒〜数十秒はアクセスが失敗する「立ち上がりの谷」が存在します。復帰直後に一斉リトライせず、少し待って段階的に再試行するのが安全です。
5. 復帰に強いアプリの作り方
原則はひとつです。「接続やハンドルはスリープをまたいで生き残らない」前提で、いつでも立て直せる構造にする。
復帰を検知して立て直す。トップレベルウィンドウの WM_POWERBROADCAST で PBT_APMRESUMEAUTOMATIC を受けたら、持っている接続を破棄して張り直します。ポイントは復帰通知だけに頼らないことです。通知の取りこぼしも通知前の通信も現実にあるので、「通信エラーを検知したら再接続する」経路を必ず併設し、復帰通知はそれを早めるトリガーと位置づけます。
// C#: 復帰通知と通信エラーの両方から同じ再接続処理に合流させる
protected override void WndProc(ref Message m)
{
const int WM_POWERBROADCAST = 0x0218;
const int PBT_APMRESUMEAUTOMATIC = 0x0012;
if (m.Msg == WM_POWERBROADCAST && (int)m.WParam == PBT_APMRESUMEAUTOMATIC)
{
_connectionManager.RequestReconnect(); // 冪等な再接続要求
}
base.WndProc(ref m);
}
再接続処理そのものは、冪等(何度呼ばれても安全)に作り、失敗時は指数バックオフ付きで再試行し、定常時はキープアライブで接続の生死を早期検知する ── この3点セットにしておくと、スリープ復帰だけでなくネットワーク瞬断や機器の再起動にもそのまま耐えます。
flowchart TB
accTitle: 復帰に強い再接続設計
accDescr: 復帰通知と通信エラーとキープアライブ失敗のどれもが同じ冪等な再接続処理に合流し、失敗時は指数バックオフで再試行する
e1["復帰通知(PBT_APMRESUMEAUTOMATIC)"] --> r["冪等な再接続処理"]
e2["通信エラー検知"] --> r
e3["キープアライブ失敗"] --> r
r --> ok{"成功?"}
ok -->|"はい"| run["通常運転へ"]
ok -->|"いいえ"| back["指数バックオフ後に再試行"]
back --> r
図7: 再接続を1本の冪等な処理に集約し、復帰通知・エラー検知・キープアライブのどこからでも同じ道に入る。
時間の扱いを見直す。「前回からの経過時間」を使う処理は、異常に大きな差分を検出したら区間を無効化する(平均に混ぜない、タイムアウト扱いにしない)ガードを入れます。復帰をまたいだ経過時間の計測には、スリープ中も進む時刻(実時間)と、処理に使った時間を区別する意識が必要です。
スリープさせたくない区間は明示的に抑止する。データ移行や装置との連続通信など、スリープされては困る処理の間は、SetThreadExecutionState(ES_CONTINUOUS | ES_SYSTEM_REQUIRED) でシステムを起こしたままにできます(画面も維持したいなら ES_DISPLAY_REQUIRED を追加)。45 より行儀のよい方法が、理由の文字列を添えられる電源要求API(PowerCreateRequest + PowerSetRequest)で、powercfg /requests に「誰がなぜ妨げているか」が表示されます。8 なお SetThreadExecutionState の抑止はスレッド単位で、解除は設定と同じスレッドから行います。スレッドが替わるasync/await等の処理では、ハンドルで管理する電源要求側を使ってください。 注意点があります。第一に、これらが抑止するのは無操作による自動スリープです。ユーザーがフタを閉じる・スタートメニューからスリープを選ぶといった明示的な操作までは止められないため、抑止中であっても本章の再接続設計は省けません。第二に、Modern Standby機のバッテリー駆動では、これらの電源要求もスリープタイムアウト経過後しばらくすると打ち切られます。中断できない処理はAC電源や運用側で担保してください。8 第三に、処理が終わったら必ず解除すること。解除漏れは「このPC、なぜかスリープしなくなった」という新しい不具合になります。
flowchart TB
accTitle: スリープ抑止の2つの手段
accDescr: 手軽に使えるSetThreadExecutionStateと、理由の文字列を添えられて管理者からpowercfgで見える電源要求APIのどちらを使う場合でも、処理の終了時に必ず解除する
need["スリープされたくない処理区間"] --> a["SetThreadExecutionState"]
need --> b["電源要求(PowerSetRequest)"]
a -.-> a1["手軽・フラグ指定のみ"]
b -.-> b1["理由付き・powercfgで可視"]
a --> off["処理終了時に必ず解除"]
b --> off
図8: どちらの手段でも「終わったら解除」が絶対条件。理由を可視化できる電源要求のほうが運用には親切。
サービス・ウィンドウレスアプリは RegisterSuspendResumeNotification(DEVICE_NOTIFY_CALLBACK)でコールバック通知を受けます。7 常時稼働が本当に要件なら、そもそもスリープするクライアントPCに常駐させる設計を見直し、サーバー側かスリープしない運用の端末に寄せるのが根本解です。
6. 調べ方 ── powercfgとイベントログ
電源まわりの調査は、OS付属の道具立てがよくできています。
- スリープしない:
powercfg /requestsで、電源要求を出しているプロセス・ドライバーが一覧できます。「アプリがSetThreadExecutionStateを解除し忘れている」もここで見つかります。 - 勝手に起きる:
powercfg /lastwakeで直前の復帰要因、powercfg /waketimersで復帰予約中のタイマーが確認できます。 - Modern Standbyの品質:
powercfg /sleepstudyでスリープ区間ごとの消費電力とアクティビティのレポートが生成されます。9 - 時系列の確認: イベントログ(システム)の Kernel-Power ソースに、スリープ入り・復帰の記録が残ります。アプリのログと突き合わせれば、「エラーの直前に復帰があったか」を客観的に確認できます。
flowchart TB
accTitle: 電源トラブルの症状と調査コマンドの対応
accDescr: スリープしない症状はpowercfg /requestsで電源要求の主を調べ、勝手に起きる症状は/lastwakeと/waketimersで復帰要因を調べ、時系列の確認はイベントログのKernel-Powerで行う
s1["スリープしない"] --> c1["powercfg /requests"]
s2["勝手に起きる"] --> c2["powercfg /lastwake・/waketimers"]
s3["時系列を確認したい"] --> c3["イベントログのKernel-Power"]
c1 -.-> note["解除し忘れたスリープ抑止も判明"]
図9: 症状と調査コマンドの対応は3系統。まず「直前にスリープしたか」を確かめてから使い分ける。
問い合わせ対応では、まず「直前にPCはスリープしていましたか(フタを閉じたか)」と聞くだけで、切り分けが一気に速くなります。
7. まとめ
- スリープは拒否できない。事前通知(PBT_APMSUSPEND)は約2秒の猶予付きベストエフォートで、緊急時には来ない。設計の本体は復帰側に置く。
- 復帰通知は PBT_APMRESUMEAUTOMATIC(サスペンド復帰時)+ PBT_APMRESUMESUSPEND(ユーザー操作時)。通知が来ない場合に備えエラー検知の再接続を本線に。
- 接続・ハンドルは復帰をまたいで生き残らない前提で、冪等な再接続+指数バックオフ+キープアライブの3点セットを実装する。
- 経過時間ベースの処理には「異常な差分」へのガードを入れる。定時処理はスリープ中に走らないことを前提に設計する。
- スリープされては困る区間は
SetThreadExecutionStateや電源要求で明示的に抑止し、終わったら必ず解除する。 - 調査は
powercfg(/requests・/lastwake・/sleepstudy)とKernel-Powerイベントログ。問い合わせ対応では「直前にスリープしたか」をまず聞く。
スリープは、アプリから見れば「予告なしに時間が飛び、周辺との接続が切れて戻ってくる」イベントです。これを異常事態ではなく日常の一部として設計に織り込めているかどうかが、ノートPC時代の業務アプリの安定性を分けます。
関連記事
- シリアル通信アプリの落とし穴 - 再接続とログ設計まで
- アプリから見たWindowsのシャットダウン ── 終了通知・再起動・電源断を正しく生き延びる
- Windows効率モードとは - 緑の葉アイコンとオフにする方法
- WindowsでSleep(1)よりイベント待機を優先すべき理由
- 「応答なし」の正体 ── Windowsがアプリを「固まった」と判定する仕組みと、固まらない設計
関連する相談領域
合同会社小村ソフトでは、「スリープ復帰後に通信が壊れる」「装置との接続が昼休み後に切れる」といった不具合の原因調査、既存アプリへの再接続ロジック・電源イベント対応の後付け実装、ノートPC運用を前提とした業務アプリ・装置制御ソフトの設計レビューを扱っています。
参考リンク
-
Microsoft Learn, PBT_APMSUSPEND event. コンピューターがサスペンド状態に入る直前に届くイベントであること、アプリはデータ保存に必要な処理を完了させるべきこと、システムがこの通知の処理に許すのはおよそ2秒であり、それを超えて処理を続けるアプリは中断されうることについて。 ↩ ↩2
-
Microsoft Learn, System Power Management Events. システムがスリープ等の動作モード変化を事前にブロードキャストすること、アイドルによるスリープ前にPBT_APMSUSPENDが通知されファイルクローズやデータ保存の準備ができること、緊急サスペンド(クリティカルバッテリーなど)では事前通知が行われないこと、このメッセージの処理にはアプリごとに最大2秒が許されタイムアウト後は打ち切られること、復帰時には全アプリに通知されることについて。 ↩ ↩2 ↩3
-
Microsoft Learn, PBT_APMRESUMESUSPEND event. ユーザー操作による復帰やその後のユーザー入力検知の際にPBT_APMRESUMEAUTOMATICに続いて送られること、リモートウェイクなど外部要因の復帰ではPBT_APMRESUMEAUTOMATICのみが送られること、アプリはスリープ時に閉じたファイルを開き直しユーザー入力に備えるべきことについて。 ↩ ↩2
-
Microsoft Learn, WM_POWERBROADCAST message. 復帰時に必ずPBT_APMRESUMEAUTOMATICが送られ、ユーザー入力による復帰ではさらにPBT_APMRESUMESUSPENDが送られること、このメッセージでは低電力状態の種類は区別できないこと、電源状態遷移の詳細はシステムイベントログに記録されること、システムの低電力状態への移行を防ぐにはSetThreadExecutionStateを呼ぶことについて。 ↩ ↩2 ↩3 ↩4
-
Microsoft Learn, SetThreadExecutionState function (winbase.h). ES_SYSTEM_REQUIREDやES_DISPLAY_REQUIREDによりシステムのアイドルスリープや画面消灯を抑止できること、ES_CONTINUOUSで継続的な抑止を宣言し、用が済んだらES_CONTINUOUS単独で呼んで解除する使い方について。 ↩ ↩2
-
Microsoft Learn, Prepare software for modern standby. Modern Standbyへの移行の最初の段階でDesktop Activity Moderator(DAM)がデスクトップアプリを一時停止すること、その後システムが低電力フェーズ・レジリエンシーフェーズへ段階的に移行し、許可されたコンポーネントだけが間欠的に動作することについて。 ↩ ↩2
-
Microsoft Learn, RegisterSuspendResumeNotification function (winuser.h). サスペンド・レジューム通知の受信を登録するAPIで、ウィンドウハンドル宛のメッセージ配送に加え、DEVICE_NOTIFY_CALLBACKを指定することでウィンドウを持たないアプリやサービスがコールバックで通知を受け取れることについて。 ↩ ↩2
-
Microsoft Learn, PowerSetRequest function (winbase.h). PowerCreateRequestで作成した電源要求オブジェクトに対しシステム維持・画面維持などの要求種別を設定できること、診断用の理由文字列を添えられ、未解決の電源要求はpowercfg /requestsで列挙できることについて。 ↩ ↩2
-
Microsoft Learn, Modern standby SleepStudy. powercfg /sleepstudyで生成されるレポートにより、Modern Standby区間ごとの消費電力・アクティビティ・復帰要因(電源ボタン、ユーザー入力、ウェイクタイマーなど)を確認できることについて。 ↩
関連する記事
同じタグを共有する最新の記事です。さらに近い話題で知識を深められます。
DllMainとローダーロック ── 「DLLの初期化で何もするな」と言われる本当の理由
DllMainでLoadLibraryやスレッド同期をしてはいけないのはなぜか。全DLL通知を直列化するローダーロックの仕組みから、デッドロックが成立する典型シナリオ、遅延初期化などの正しい設計、ハング調査の手順までを一次情報で解説します。
「応答なし」の正体 ── Windowsがアプリを「固まった」と判定する仕組みと、固まらない設計
Windowsの「応答なし」は、ウィンドウが5秒間メッセージを取り出さないとOSが判定し、幽霊ウィンドウに差し替える仕組みです。判定の内部動作から、固まる定番原因、UIスレッドから重い処理を追い出す設計、ハングの調査手順まで解説します。
マルチスレッドの実務ベストプラクティス C言語編 ── Win32 APIの流儀で安全に書く
C言語×Win32のマルチスレッドは、_beginthreadexでのスレッド作成、SRWロックと条件変数、Interlocked、停止イベント+WaitForMultipleObjectsの停止設計が定石。TerminateThreadの危険とDllMainの制約まで整理...
Windowsアプリ 外注・受託開発を依頼する前に整理したいこと
Windowsアプリの外注・受託開発を依頼する前に、既存ソフト改修、装置連携、COM/ActiveX、配布・更新、保守の整理ポイントを解説します。
Win32スレッドプールAPI ── CreateThreadpoolWorkで「スレッドを作らない」並行処理
ネイティブコードでCreateThreadを乱立させていませんか。Vistaで刷新されたWin32スレッドプールAPIのwork・timer・wait・ioの4オブジェクト、クリーンアップグループ、コールバックでの禁止事項までを一次情報にもとづいて解説します。
関連トピック
このテーマと近いトピックページです。記事を起点に、関連するサービスや他の記事へ進めます。
Windows技術トピック
Windows 開発、不具合調査、既存資産活用の技術トピックをまとめた入口です。
不具合調査 / 長期稼働テーマ
再現しにくい不具合、通信停止、長期稼働障害、失敗パス検証を整理するトピックです。
このテーマがつながるサービス
この記事は次のサービスページにつながります。近い入口からご覧ください。
Windowsアプリ開発
業務アプリ、装置連携、通信ツールなどの Windows ソフト開発を支援します。
不具合調査・原因解析
再現しにくい障害、長期稼働後の不具合、通信停止などの調査を支援します。
よくある質問
この記事のテーマについて、相談時によくある質問をまとめています。
- アプリはスリープに入ることを事前に知って拒否できますか?
- 現在のWindowsでは、通知は受け取れますが拒否はできません。スリープ直前にはWM_POWERBROADCASTメッセージでPBT_APMSUSPENDイベントが届き、ここでファイルを閉じる・状態を保存するなどの準備ができますが、処理に許される時間はアプリごとにおよそ2秒で、超えるとシステムはアプリを待たずに進みます。またバッテリー切れ間際などの緊急サスペンドでは事前通知そのものが来ません。したがって「スリープ前に必ず完了させる」設計は成立せず、「いつ切られても復帰時に立て直せる」設計にする必要があります。どうしてもスリープさせたくない処理区間は、SetThreadExecutionStateや電源要求(PowerSetRequest)で明示的に抑止します。
- 復帰したことはどうやって検知すればよいですか?
- ウィンドウを持つアプリならWM_POWERBROADCASTを処理します。サスペンドからの復帰時にはPBT_APMRESUMEAUTOMATICが届き、ユーザーの操作(電源ボタンやキー入力)による復帰の場合はその後にPBT_APMRESUMESUSPENDも届きます。無人で復帰してすぐ再スリープする場合はPBT_APMRESUMEAUTOMATICしか来ないため、再接続などの必須処理はPBT_APMRESUMEAUTOMATIC側に置き、画面更新などユーザー向けの処理をPBT_APMRESUMESUSPEND側に置く使い分けが基本です。ウィンドウを持たないサービスやコンソールアプリは、RegisterSuspendResumeNotificationをDEVICE_NOTIFY_CALLBACK指定で使えばコールバックで同じ通知を受け取れます。
- スリープ中もアプリを動かし続けることはできますか?
- 原則としてできません。スリープ中はCPUの実行自体が止まり(Modern Standby機ではデスクトップアプリはDesktop Activity Moderatorにより一時停止され)、アプリのコードは走りません。選択肢は2つです。ひとつは、処理中だけスリープを抑止すること。SetThreadExecutionStateにES_SYSTEM_REQUIREDを指定するか、PowerCreateRequest/PowerSetRequestで電源要求を出せば、無操作による自動スリープをその間抑止できます(powercfg /requestsで確認できます)。ただしユーザーがフタを閉じるなどの明示的なスリープ操作までは止められないため、抑止中でも復帰への備えは必要です。もうひとつは、スリープを受け入れて「復帰後に追いつく」設計にすることです。夜間バッチのような定時処理は、タスクスケジューラの「タスクの実行時にスリープを解除する」でPCを起こす方法もあります。常時稼働が本当に必要な処理は、スリープしない設定のサーバーやサービスに寄せるのが正道です。
- 復帰後にTCP接続やシリアルポートが使えなくなるのはなぜですか?
- スリープ中はネットワークアダプターやUSBデバイスも省電力状態に落ちるためです。TCP接続は相手側やNAT・ファイアウォールのタイムアウトで破棄されており、復帰後の送受信はエラーになります(エラーになるまで気づけないことも多い)。USBシリアル変換器などは復帰時にデバイスの取り外し・再接続として扱われることがあり、開いていたハンドルは無効になります。どちらも「ハンドルや接続は復帰をまたいで生き残らない」前提で、復帰通知や通信エラーを契機に接続を張り直す再接続ロジックを実装するのが正解です。定期的なキープアライブと、失敗時の指数バックオフ付き再試行を組み合わせるのが定石です。
- 勝手にスリープする・勝手に復帰する原因はどう調べますか?
- powercfgコマンドが第一の道具です。「スリープしない」方向ではpowercfg /requestsで、どのプロセス・ドライバーが電源要求を出してスリープを妨げているかが一覧できます。「勝手に起きる」方向ではpowercfg /lastwakeで直前の復帰要因が、powercfg /waketimersで復帰予約中のタイマーが確認できます。Modern Standby機ではpowercfg /sleepstudyでスリープ中の消費とアクティビティのレポートが出ます。また、スリープ・復帰の履歴はイベントログ(システムログのKernel-Powerソース)に記録されるため、「いつスリープし、いつ何が理由で起きたか」を時系列で確認できます。