この章は「Windows のプロセス」をテーマに、System.Diagnostics を使った実務的な監視と制御をまとめます。
要件にある各項目を順番に、用途の説明つきで整理し、最後に ProcessStartInfo を使った起動のコントロールや OS Verb まで実装例を示します。
本記事は Windows 向けの API 解説が中心です。Linux/Mac でも Process 自体は使えますが、OS Verb や一部のプロパティの挙動は異なります。
Windows プロセスの役割
Windows の プロセス は、実行中プログラムの「実体」を表す OS の管理単位です。
OS は、各プロセスに以下のような責任を与えます。
- 仮想メモリ空間の確保(自前のメモリ領域)
- ハンドル/リソース(ウィンドウ、ファイル、イベント、デバイス等)の所有
- セキュリティ識別子と実行権限(どのユーザー権限で動くか)
- ライフサイクル管理(起動、実行、待機、終了)
プロセスは Id(PID)で一意に管理され、OS はこの PID を中心にログ、監視、終了処理、依存関係を扱います。
スレッドの役割と Windows のプロセス/スレッド関係
スレッドは「CPU 上で実際に走る命令の流れ」です。
プロセスは「箱」、スレッドは「その箱の中のワーカー」と考えるとわかりやすいです。
- プロセスは少なくとも 1 スレッドを持つ
- 複数スレッドを持つと CPU の並行処理が可能
- スレッドは同じプロセス内ではアドレス空間を共有し、別プロセス間では分離
- 結果として、CPU 負荷や応答性の調整は「スレッド設計」と「スレッド属性」に依存しやすい
プロセスは「誰が」「どこで」「何を持つか」、スレッドは「実際にどの命令をどの順序で実行するか」を担います。
.NET Core でプロセスを操作する
System.Diagnostics 名前空間にある Process クラスを使うと、実行中プロセスの列挙、調査、制御ができます。
Process は .NET Core / .NET 5+ でも使えますが、UseShellExecute の挙動や OS Verb は Windows 側で恩恵が大きいです。
Process を使うときの基本的な考え方は、次の 3 つです。
- 読む:
GetProcesses()やGetProcessById()で対象プロセスを取得し、名前、PID、開始時刻、スレッド、モジュールなどを調べる - 起動する:
Process.Start()やProcessStartInfoで外部プログラムを起動する - 止める:
CloseMainWindow()で通常終了を依頼し、必要に応じてKill()で強制終了する
ただし、プロセス情報は OS が管理する情報です。
そのため、対象プロセスの権限、実行ユーザー、32-bit / 64-bit の違い、OS の制限によって、一部のプロパティ取得で例外が発生することがあります。
特に Modules、StartTime、Threads などは、対象プロセスや権限によって取得できない場合があります。
監視ツールや管理ツールとして使う場合は、プロセス全体を止めないように、プロセス単位またはプロパティ単位で try / catch する設計にしておくと扱いやすくなります。
System.Diagnostics 名前空間のプロセス関連型
以下は「プロセス中心の型」一覧です。
それぞれの型がどのような場面で役立つのかを整理します。
Process
- 実行中プロセスそのものを表現する。PID、メインウィンドウ情報、開始時刻、終了、停止、スレッド・モジュール情報などの取得と操作ができる中心クラス。
ProcessModule
- 1 つのモジュール(DLL/EXE)情報を表現。モジュール名、ファイルバージョン、基底アドレス、ロケーションなどを取得して、実行ファイル依存関係を理解する。
ProcessModuleCollection
ProcessModuleの集合を表現。対象プロセスがロード済みモジュールをまとめて反復処理し、依存ファイルの監査・診断に使う。
ProcessStartInfo
- 外部プロセス起動時の設定(実行ファイル、引数、作業ディレクトリ、ウィンドウ表示、リダイレクト、Verb など)を保持し、起動動作を制御する。
Process.Start(ProcessStartInfo)とセットで使うのが本筋。
ProcessThread
- 対象プロセス内の 1 本の OS スレッドを表す。優先度、開始時刻、CPU 時間、待機理由などを直接参照できる。
ProcessThreadCollection
ProcessThreadの集合を表現。1 つのプロセス内の全スレッドを列挙して診断・負荷分析に使う。
Process 型の主なプロパティ
ExitTime
- 終了時刻。プロセス寿命の終点を知ることで、クラッシュ/正常終了のあとに実行期間と監査を追跡できる。
Handle
- OS 上のプロセスハンドル。低レベル API で使う識別子に近い存在で、必要最小限の制御やログに利用する。
Id
- PID。システム上でプロセスを一意に識別し、別 API 連携時のキーとして使う。
MachineName
- プロセスが属するマシン名。リモート情報や複数端末運用時の識別に使う(環境によっては取得不可なことがある)。
MainWindowTitle
- メインウィンドウのタイトル。GUI 系プロセスの識別や状態可視化に使う。
Modules
- 当該プロセスが読み込んでいるモジュール集合。依存 DLL の監査や不正注入の前段チェックに使う入口。
ProcessName
- 実行ファイル名ベースのプロセス名。一覧表示・フィルタリングの第一情報。
Responding
- 応答状態(ハングしそうか)を示す。UI 応答不能検出や監視ダッシュボードの品質指標に利用。
StartTime
- 起動時間。稼働秒数や起動タイミングの相関分析に使える。
Threads
- 対象プロセスのスレッド集合。CPU 消費が大きい候補の調査、デッドロック傾向の可視化などに使う。
Process 型の主なメソッド
CloseMainWindow()
- メインウィンドウへ終了要求を送る。強制終了よりも穏やかな終了パスを優先したいときに使う。戻り値は受け付け可否。
GetCurrentProcess()
- 現在の .NET プロセス(自プロセス)を取得。自己監視(同一プロセスの PID / メモリ / スレッド)に便利。
GetProcesses()
- 現在のマシン上で実行中の全プロセスを取得。監査・一覧表示・検索の入口。
Kill()
- プロセスを強制終了する。
CloseMainWindowが効かない場合の最終手段(副作用が大きい)。
Start()
- 新規プロセス起動の起動点。
Process.Start(string)、Start(ProcessStartInfo)などで起動条件を変えられる。
実行中プロセスを列挙する
using System;
using System.Diagnostics;
using System.Linq;
foreach (var process in Process.GetProcesses().OrderBy(p => p.ProcessName))
{
using (process)
{
Console.WriteLine($"{process.ProcessName} (ID: {process.Id})");
}
}
補足
Process.GetProcesses()は配列で返すため、その場で全件を取得できます。- 一部プロパティは権限不足で例外が出ることがあるので、必要なら
try / catchで個別処理すると安全です。
特定のプロセスを調査する
using System;
using System.Diagnostics;
using System.Linq;
var processes = Process.GetProcessesByName("notepad");
var process = processes.FirstOrDefault();
if (process == null)
{
Console.WriteLine("対象のプロセスは見つかりませんでした。");
return;
}
using (process)
{
try
{
Console.WriteLine($"PID: {process.Id}");
Console.WriteLine($"名前: {process.ProcessName}");
Console.WriteLine($"開始時刻: {process.StartTime}");
Console.WriteLine($"応答状態: {process.Responding}");
Console.WriteLine($"メインウィンドウ: {process.MainWindowTitle}");
Console.WriteLine($"マシン: {process.MachineName}");
}
catch (Exception ex)
{
Console.WriteLine($"取得に失敗: {ex.Message}");
}
}
プロセスのスレッド集合を調査する
using System;
using System.Diagnostics;
using var process = Process.GetCurrentProcess();
Console.WriteLine($"対象: {process.ProcessName} ({process.Id})");
foreach (ProcessThread thread in process.Threads)
{
try
{
Console.WriteLine($"ThreadId={thread.Id}, " +
$"State={thread.ThreadState}, Priority={thread.PriorityLevel}, " +
$"TotalCPU={thread.TotalProcessorTime}");
}
catch (Exception ex)
{
Console.WriteLine($"Thread 情報取得失敗: {ex.Message}");
}
}
ProcessThread 型の主なメンバー
CurrentPriority
- OS が持つ現在の優先度を反映する。スケジューリングの実効優先度を読む時に使う。
Id
- OS 上のスレッド ID。スレッド単位で識別し、イベントログ等と突き合わせる。
IdealProcessor
- スレッドが「できれば」実行されたい CPU コア。CPU バランシングの制御ヒント。
PriorityLevel
- スレッドの優先度クラス。高負荷時の応答特性を決める一つの軸。
ProcessorAffinity
- 実行可能な CPU コアをビットマップで示す。CPU 配置の拘束・分離に使う。
StartAddress
- スレッドの開始アドレス。高度な診断(デバッグ/不正検知/プロファイリング)で有用。
StartTime
- スレッドの開始時刻。スレッド寿命の計測に使う。
ThreadState
- 実行状態(待機、実行中など)を示す。停止・待機を見分ける基本情報。
TotalProcessorTime
- 生存期間における総 CPU 使用時間。CPU 負荷ボトルネック解析の素地。
WaitReason
- 待機中の理由(例: Lock/IO 等)。原因切り分けに直接使える。
WaitReason は、スレッドが待機状態のときに意味を持ちます。
スレッド状態によっては取得時に例外になることがあるため、診断コードでは ThreadState を見ながら扱うのが安全です。
プロセスのモジュール集合を調査する
using System;
using System.Diagnostics;
using var process = Process.GetCurrentProcess();
foreach (ProcessModule module in process.Modules)
{
try
{
Console.WriteLine($"{module.ModuleName}");
Console.WriteLine($" Path: {module.FileName}");
Console.WriteLine($" BaseAddr: {module.BaseAddress}");
Console.WriteLine($" Size: {module.ModuleMemorySize}");
}
catch (Exception ex)
{
Console.WriteLine($"モジュール情報取得失敗: {ex.Message}");
}
}
PID を指定してプロセスを調査する
プロセス名は同じ名前のプロセスが複数存在することがあります。
明確に 1 つのプロセスを調べたい場合は、PID を指定する GetProcessById() が便利です。
using System;
using System.Diagnostics;
Console.Write("PID を入力してください: ");
var input = Console.ReadLine();
if (!int.TryParse(input, out var pid))
{
Console.WriteLine("PID は数値で入力してください。");
return;
}
try
{
using var process = Process.GetProcessById(pid);
Console.WriteLine($"PID: {process.Id}");
Console.WriteLine($"名前: {process.ProcessName}");
Console.WriteLine($"開始時刻: {process.StartTime}");
Console.WriteLine($"スレッド数: {process.Threads.Count}");
}
catch (ArgumentException)
{
Console.WriteLine("指定された PID のプロセスは存在しません。");
}
catch (Exception ex)
{
Console.WriteLine($"プロセス情報の取得に失敗しました: {ex.Message}");
}
GetProcessesByName() は「名前から候補を探す」用途、GetProcessById() は「特定の 1 件を調べる」用途に向いています。
プログラムからプロセスを起動・停止する
using System;
using System.Diagnostics;
using System.Threading;
using var process = Process.Start("notepad.exe");
if (process == null)
{
Console.WriteLine("プロセス起動に失敗しました。");
return;
}
Console.WriteLine($"起動: {process.ProcessName} (PID: {process.Id})");
Thread.Sleep(2_000);
// まずは穏やかに終了を試みる
if (process.CloseMainWindow())
{
process.WaitForExit(2_000);
}
// まだ残るなら強制終了
if (!process.HasExited)
{
process.Kill();
process.WaitForExit();
}
Console.WriteLine("終了しました。");
停止処理では、いきなり Kill() を呼ぶよりも、まず CloseMainWindow() を試す方が自然です。
GUI アプリケーションであれば保存確認や終了処理を実行できる可能性があります。
一方、コンソールアプリやサービスのようにメインウィンドウを持たないプロセスでは、CloseMainWindow() が効果を持たないことがあります。
その場合は HasExited を確認した上で、必要に応じて Kill() を使います。
ProcessStartInfo クラスでプロセス起動を制御する
using System;
using System.Diagnostics;
var psi = new ProcessStartInfo
{
FileName = "cmd.exe",
Arguments = "/c echo ProcessStartInfo sample && timeout /t 1",
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = true,
WindowStyle = ProcessWindowStyle.Hidden,
WorkingDirectory = Environment.CurrentDirectory
};
using var process = new Process { StartInfo = psi };
process.Start();
var output = process.StandardOutput.ReadToEnd();
var error = process.StandardError.ReadToEnd();
process.WaitForExit();
Console.WriteLine($"ExitCode: {process.ExitCode}");
Console.WriteLine($"stdout: {output}");
Console.WriteLine($"stderr: {error}");
ポイント:
UseShellExecute = falseにすると、標準入出力リダイレクトが使いやすくなるCreateNoWindow = trueは CLI 起動時の見え方を最小化ProcessStartInfo単体で起動設定を組み立て、Process.Start(psi)として実行してもよい
UseShellExecute は重要な分岐点です。
標準出力や標準エラーを読み取りたい場合は、基本的に UseShellExecute = false を選びます。
一方、OS の関連付け、Verb、管理者実行(runas)などを使いたい場合は UseShellExecute = true が必要になります。
ProcessStartInfo で OS Verb を活用する
Verb は「このファイル/アプリを OS にどう扱ってほしいか」を指定します(Windows 特有の思想)。
例えば open, edit, print, runas などがあります。
using System.Diagnostics;
// 1) 既定の関連アプリで開く
var open = new ProcessStartInfo
{
FileName = @"C:\Windows\System32\notepad.exe",
Arguments = @"C:\Temp\sample.txt",
UseShellExecute = true
};
Process.Start(open);
// 2) 管理者権限で起動(UAC が要求される場合あり)
var runAs = new ProcessStartInfo
{
FileName = "cmd.exe",
Arguments = "/c whoami",
Verb = "runas",
UseShellExecute = true
};
// Process.Start(runAs); // 実行環境で UAC ダイアログが表示される場合あり
// 3) Print verb(対応しているファイル種別と関連付けが前提)
var print = new ProcessStartInfo
{
FileName = @"C:\Temp\sample.txt",
Verb = "print",
UseShellExecute = true
};
// Process.Start(print);
Verb は OS 側の関連付けに依存するため、sample.txt のように存在するファイルを使う前提で試すと再現性が高いです。
本番では HasExited や例外を追加して、起動失敗時の分岐を入れましょう。
利用可能な Verb はファイル種別や環境によって変わります。
次のように Verbs を確認すると、その環境で使える操作を調べられます。
using System;
using System.Diagnostics;
var psi = new ProcessStartInfo
{
FileName = @"C:\Temp\sample.txt",
UseShellExecute = true
};
foreach (var verb in psi.Verbs)
{
Console.WriteLine(verb);
}
まとめ
ここまでを一言で言うと、System.Diagnostics の Process 系 API は「監視」「調査」「起動と制御」を一体で扱えるレイヤです。
まず GetProcesses() で全体像を掴み、GetProcessesByName / GetProcessById で特定対象へ絞る。次に Threads や Modules で深掘りし、必要なら ProcessStartInfo で起動方針を明示し、最後に安全な停止フロー(CloseMainWindow → 必要なら Kill)を整える、という順番が扱いやすいです。