子プロセスを安全に扱うためのチェックリスト
まず結論
Windows アプリで子プロセスを安全に扱うコツは、起動 API を選ぶことではなく、プロセス木の所有者を決め、終了手順と I/O を設計することです。
実務で最も効くポイント:
- 親の生死と子プロセス木の寿命を結びつけたいなら、基準点は Job Object
- コンソールへの終了依頼(
GenerateConsoleCtrlEvent)とプロセス木の回収(Job Object)は別物 - 起動時点から Job に入れたいなら
STARTUPINFOEX+PROC_THREAD_ATTRIBUTE_JOB_LIST - 標準出力/標準エラーは並列に吸い上げるのが基本
- stdin を使うなら書き終えたら close して EOF を伝える
- watchdog は監視対象の Job の外に置く
4つの設計要素
子プロセス管理は次の4つに分けて考えます:
- プロセス木を誰が所有するか
- どうやって協調終了を依頼するか
- 標準入出力をどう流すか
- 異常終了とハングをどう監視するか
Job Object を基準にする
Job Object の一番強い点は、「誰の子か」ではなく「どの Job に属するか」でプロセス木を束ねられることです。
押さえたい4つのポイント
- 親終了で木ごと片づけたいなら
KILL_ON_JOB_CLOSE- 最後の job handle が閉じられたときに全プロセスが終了
- 親の異常終了時も cleanup される
- BREAKAWAY を軽く付けない
- cleanup できるつもりの木からプロセスが抜ける原因になる
- 起動時点から Job に入れたいなら
PROC_THREAD_ATTRIBUTE_JOB_LIST- 後から
AssignProcessToJobObjectするより筋が良い
- 後から
- job handle の所有者を曖昧にしない
- handle を複製・継承すると、想定どおり cleanup されない
終了伝播を3段階で設計する
- 協調終了を依頼する
- 短い timeout で待つ
- 最後に Job ごと強制終了する
この順番にすれば、正常な終了経路を保ちつつ、ハング時は回収できます。
プロセスタイプ別の終了方法
- GUI 子プロセス:
CloseMainWindow→ 待機 → Job kill - コンソール子プロセス:
CREATE_NEW_PROCESS_GROUP+CTRL_BREAK_EVENT→ 待機 → Job kill - Worker/headless: 専用の終了 protocol(stdin に quit、named pipe で shutdown 等)
標準入出力を詰まらせない
- stdout/stderr は並列 drain - 片方を全部読んでからもう片方、は詰まる
- stdin を使うなら EOF まで設計する - 書き終えたら close しないと子が待ち続ける
- 不要な pipe end を必ず閉じる - 閉じないと EOF が伝わらない
UseShellExecute=falseと handle 継承を正しく設定する
Watchdog は「外」に置く
- 監視対象と同じ Job に入れない - worker が落ちたら再起動したいのに、再起動役まで死ぬ
- exit 監視は wait handle ベース - polling ループより
WaitForSingleObjectなど - UI スレッドで無限待機しない - メッセージポンプが止まる
- hang watchdog には heartbeat が必要 - プロセスが生きているだけではハングは判定できない
- 再起動役は監視対象の外に置く - worker tree と restart authority を分離
- restart policy は budget で持つ - backoff、回数上限、連続失敗時の停止
典型パターン別の推奨構成
| 場面 | 推奨構成 |
|---|---|
| 単発 CLI helper 起動 | 1起動=1Job。KILL_ON_JOB_CLOSE、stdio 並列 drain |
| helper が孫プロセスを起動 | Job Object 前提、breakaway 禁止 |
| 長時間 worker tree 監視 | watchdog は外部プロセス。世代ごとに Job 作成 |
| コンソールツールを丁寧に停止 | CREATE_NEW_PROCESS_GROUP + CTRL_BREAK_EVENT |
| GUI helper を閉じる | CloseMainWindow → timeout → Job kill |
やってはいけないこと
Kill(entireProcessTree: true)だけで tree lifecycle が解けたと思うstdoutを全部読んでからstderrを読む- pipe の未使用 end を閉じない
- UI スレッドで
WaitForSingleObject(INFINITE)する - watchdog を監視対象と同じ Job に入れる
まとめ
子プロセス管理で最も効くのは次の4つを先に決めることです:
- 誰が process tree を所有するか
- どうやって終了要求を伝えるか
- 標準入出力をどう流し切るか
- watchdog をどこへ置くか
CreateProcess や Process.Start は入口にすぎません。本当に事故率に効くのは終了責任の所在とI/O の流し切りです。
関連する記事
同じタグを共有する最新の記事です。さらに近い話題で知識を深められます。
Windowsシングルバイナリ化の限界と実践
Windows アプリを 1 EXE にしたいときの「配布物を 1 個にする」と「OS 依存を消す」の違いを、.NET、C++、WebView2、WinUI、サービス、ドライバまで段階別に整理し、技術選定と配布設計の判断軸が分かります。
共有メモリの安全な使い方
共有メモリを実務で扱うときの典型的な落とし穴を整理し、同期、可視性、寿命、ABI、権限、クラッシュ復旧まで踏まえた事故率の低い設計の出発点を、Windows と POSIX の両方の API を交えながら具体的に示します。
C#をNative AOTでDLL化しC/C++から呼び出す方法
C# のクラスライブラリを Native AOT でネイティブ DLL として発行し、UnmanagedCallersOnly で C/C++ から呼び出す構成を、設計指針・最小実装・はまりどころまでまとめて整理した実務向け解説記事です。
Windowsはなぜ今の形になったのか:開発者から見た歴代Windowsの進化
Windows 95からWindows 11までの変化を、見た目の年表ではなく、互換性、安定性、権限管理、ドライバ、Win32、.NET、セキュリティなどWindowsアプリ開発者の視点で整理します。
ClickOnce 入門:配布・更新・選定基準
ClickOnce の仕組みと向き不向きを実務目線で整理します。マニフェスト、自動更新、キャッシュ分離、署名、配布経路の注意点に加え、社内業務アプリで強い理由と MSI/MSIX が適するケースの見分け方が分かります。
関連トピック
このテーマと近いトピックページです。記事を起点に、関連するサービスや他の記事へ進めます。
Windows技術トピック
Windows 開発、不具合調査、既存資産活用の技術トピックをまとめた入口です。
このテーマがつながるサービス
この記事は次のサービスページにつながります。近い入口からご覧ください。
Windowsアプリ開発
業務アプリ、装置連携、通信ツールなどの Windows ソフト開発を支援します。