process-api-net11 について
.NET 11以降の`System.Diagnostics.Process`新APIを扱うスキルです。外部プロセスの実行と出力のデッドロックを避けた読み取りに加え、子プロセスの終了動作やハンドル継承を制御できます。
主な機能
- 01プロセスの実行と終了待機
- 02テキスト、バイト、行の出力取得
- 03タイムアウトやキャンセル時の終了
- 04子プロセスの終了動作制御
- 05ハンドル継承やデタッチ起動の管理
活用シーン
- 01CLIコマンドの出力を安全に取得
- 02親プロセス終了時に子プロセスを停止
- 03ハンドルを保持せず補助プログラムを起動
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ dotnet/ skills --skill process-api-net11インストール後、Claude Code で/process-api-net11を入力して読み込まれたか確認します。
詳細
Process API の改善 — .NET 11
.NET 11 で System.Diagnostics.Process に追加された新しい API により、プロセス管理が簡素化され、定型コードが削減され、.NET 11 以降のアプリケーションで出力をキャプチャする際によく発生するデッドロックパターンを防止できます。
使用する場面
- .NET 11(またはそれ以降)を対象とするプロジェクトで、外部プロセスを実行またはオーケストレーションする場合。
- プロセスを起動して終了を待ち、デッドロックのリスクなしに出力ストリームとエラーストリームをキャプチャする必要がある場合(
Process.RunAndCaptureText[Async])。 - 親プロセスの終了時に子プロセスも自動的に終了するようにしたい場合(
KillOnParentExit)。 - 対応プラットフォームで
SafeProcessHandleを使った軽量かつ低オーバーヘッドなプロセス作成が必要な場合。 - ハンドルの継承をきめ細かく制御する必要がある場合(
InheritedHandles)、またはプロセスをデタッチして起動する場合(StartDetached)。
使用しない場面
- プロジェクトの対象が .NET 10 以前の場合 — これらの API は .NET 11 より前では利用できません。
- 既定の
Process.Start()で十分であり、出力のキャプチャや高度なライフサイクル規則が必要ない場合。
対象フレームワーク
<TargetFramework>net11.0</TargetFramework>
新しい API
型
新しい便利メソッドを使用する前に、以下の型を確認してください。フレームワークの型をそのまま使用します。
| 型 | 種類 | 読み取り専用プロパティ |
|---|---|---|
ProcessExitStatus | シールドクラス | int ExitCode、bool Canceled、PosixSignal? Signal |
ProcessTextOutput | シールドクラス | ProcessExitStatus ExitStatus、string StandardOutput、string StandardError、int ProcessId |
ProcessOutputLine | 読み取り専用構造体 | string Content、bool StandardError |
ProcessExitStatus は、終了したプロセスの結果を表します。ExitCode は外部コマンドの仕様に基づいて解釈してください。汎用的な Success プロパティはありません。ProcessTextOutput には、終了ステータス、キャプチャされた出力とエラーテキスト、およびプロセス ID が含まれます。ProcessOutputLine は、各出力行と、それが標準エラーからのものかどうかを示します。
高レベルの便利 API
静的メソッド
以下の ProcessStartInfo オーバーロードでは、UseShellExecute = false が必要です。これは .NET の既定値です。
Process.Run / Process.RunAsync
プロセスを起動して終了を待ち、終了ステータスを返します。標準出力や標準エラーはキャプチャしません。silent: true を渡すと、標準ハンドルを内部で NUL デバイスにリダイレクトし、標準出力と標準エラーを破棄します。タイムアウトまたはキャンセル時には、プロセスが強制終了されます。
public static ProcessExitStatus Run(string fileName, IEnumerable<string>? arguments = null, bool silent = false, TimeSpan? timeout = null)
public static Task<ProcessExitStatus> RunAsync(string fileName, IEnumerable<string>? arguments = null, bool silent = false, CancellationToken cancellationToken = default)
public static ProcessExitStatus Run(ProcessStartInfo startInfo, TimeSpan? timeout = null)
public static Task<ProcessExitStatus> RunAsync(ProcessStartInfo startInfo, CancellationToken cancellationToken = default)
Process.RunAndCaptureText / Process.RunAndCaptureTextAsync
プロセスを起動し、標準出力と標準エラーの両方をキャプチャして、終了を待ちます。ストリームのリダイレクトでデッドロックを防ぐのに非常に役立ちます。タイムアウトまたはキャンセル時には、プロセスが強制終了されます。
ProcessStartInfo オーバーロードを使用する場合、呼び出し元が ProcessStartInfo で RedirectStandardOutput = true と RedirectStandardError = true を設定する必要があります(BCL API は渡された入力引数を変更できないためです)。
public static ProcessTextOutput RunAndCaptureText(string fileName, IEnumerable<string>? arguments = null, TimeSpan? timeout = null)
public static Task<ProcessTextOutput> RunAndCaptureTextAsync(string fileName, IEnumerable<string>? arguments = null, CancellationToken cancellationToken = default)
public static ProcessTextOutput RunAndCaptureText(ProcessStartInfo startInfo, TimeSpan? timeout = null)
public static Task<ProcessTextOutput> RunAndCaptureTextAsync(ProcessStartInfo startInfo, CancellationToken cancellationToken = default)
Process.StartAndForget
プロセスが破棄されると同時に強制終了される、という誤解がよくあります。実際にはそうではありません。Process.Dispose はプロセスに関連付けられたリソースを解放するだけで、プロセスを強制終了しません。
破棄を気にせず簡単にプロセスを起動できるように、Process.StartAndForget が導入されました。このメソッドは実行可能ファイルを起動してその ID を返し、関連するすべてのハンドルリソースを直ちに解放します。StandardInputHandle、StandardOutputHandle、または StandardErrorHandle で指定されていない標準ハンドルは、既定でヌルデバイスに接続されます。
UseShellExecute、または RedirectStandardInput、RedirectStandardOutput、RedirectStandardError のいずれかのフラグが true の場合、StartAndForget は InvalidOperationException をスローします。出力を読み取る必要がある場合は、キャプチャ API またはストリーミング API を使用してください。
public static int StartAndForget(string fileName, IEnumerable<string>? arguments = null)
public static int StartAndForget(ProcessStartInfo startInfo)
インスタンスメソッド
これらのメソッドは Process インスタンスに対して呼び出し、標準出力と標準エラーを直接読み取ります。OS のパイプバッファーがあふれてデッドロックが発生するのを防ぎます。
注:これらのメソッドを呼び出すには、Process.Start に渡す ProcessStartInfo で RedirectStandardOutput = true と RedirectStandardError = true を設定する必要があります。
public (string StandardOutput, string StandardError) ReadAllText(TimeSpan? timeout = null)
public Task<(string StandardOutput, string StandardError)> ReadAllTextAsync(CancellationToken cancellationToken = default)
public (byte[] StandardOutput, byte[] StandardError) ReadAllBytes(TimeSpan? timeout = null)
public Task<(byte[] StandardOutput, byte[] StandardError)> ReadAllBytesAsync(CancellationToken cancellationToken = default)
public IEnumerable<ProcessOutputLine> ReadAllLines(TimeSpan? timeout = null)
public IAsyncEnumerable<ProcessOutputLine> ReadAllLinesAsync(CancellationToken cancellationToken = default)
タプルの戻り値を分解する例:
var (stdout, stderr) = await process.ReadAllTextAsync();
ProcessStartInfo のプロパティ
KillOnParentExit
現在の(親)プロセスが終了すると(致命的なクラッシュや強制終了を含む)、生成された子プロセスが確実に終了します。Windows、Linux、Android で動作します。
public bool KillOnParentExit { get; set; }
クロスプラットフォームのコードでは、設定値だけでなく、対応プラットフォームかどうかも確認してからプロパティを設定してください。自動終了が必要な場合、対応していないプラットフォームでは、保護されていない子プロセスを起動するのではなく、明示的にエラーにしてください。
KillOnParentExit を使用するには、UseShellExecute = false が必要です。
InheritedHandles
子プロセスに継承されるハンドル(ファイル記述子)をきめ細かく制御し、意図しないリソースリークを防ぎます。
- 標準ハンドル(
stdin、stdout、stderr)は常に含まれます(リストに追加する必要はありません)。 - リストを空にすると、標準ハンドルのみが継承されます。
- 現時点では、
SafeFileHandleとSafePipeHandleのインスタンスのみ指定できます。 - Windows で新しいプロセスを生成する際、グローバルロックは使用されません(複数のプロセスを並列で生成するプロジェクトの調整に重要です)。
- プロセスを同時に起動する場合、同じハンドルを
InheritedHandlesに渡さないでください。ランタイムが一時的にその継承フラグを変更します。ハンドルを共有する起動処理は直列化するか、同時起動ごとに別のハンドルを使用してください。 - 渡す前にハンドルの継承を有効にしないでください。有効にすると、他のプロセス起動 API によって意図せず継承される可能性があります。
- ネイティブなハンドル継承制御を備えていない Unix システムでは、このプロパティを設定するとプロセスの起動パフォーマンスが大幅に低下する場合があります。
- null 以外のリストを指定するには、
UseShellExecute = falseが必要です。また、空ではないUserNameと併用できません。
public IList<SafeHandle>? InheritedHandles { get; set; }
StartDetached
親のターミナルまたはジョブセッションから切り離してプロセスを起動し、親の終了後もプロセスが存続するようにします。
public bool StartDetached { get; set; }
StartDetached を使用するには、UseShellExecute = false が必要です。
例
1. 1 行で実行して出力を取得する
プロセスを実行し、デッドロックのリスクなくすべての出力テキストを安全に読み取ります。キャンセルや非同期処理のスケーラビリティが不要な単純な CLI 操作では、同期オーバーロードを使用してください。
using System;
using System.Diagnostics;
// Run 'git status' and capture output
ProcessTextOutput result = Process.RunAndCaptureText("git", ["status"]);
if (result.ExitStatus.ExitCode == 0)
{
Console.WriteLine($"Git Output: {result.StandardOutput}");
}
else
{
Console.WriteLine($"Failed with exit code: {result.ExitStatus.ExitCode}");
Console.WriteLine($"Error: {result.StandardError}");
}
2. 親プロセスの終了時に子プロセスを自動終了する
Windows、Linux、Android では、メインアプリケーションの終了時に、長時間実行されるバックグラウンドワーカープロセスが終了するようにします。他のプラットフォームでは、この例は子プロセスの起動前に停止します。
using System;
using System.Diagnostics;
ProcessStartInfo startInfo = new("dotnet", ["run", "--project", "BackgroundWorker.csproj"]);
if (OperatingSystem.IsWindows() || OperatingSystem.IsLinux() || OperatingSystem.IsAndroid())
{
startInfo.KillOnParentExit = true;
}
else
{
throw new PlatformNotSupportedException("This example requires Windows, Linux, or Android.");
}
using Process process = Process.Start(startInfo)!;
// The background worker is now tied to this process's lifecycle
3. 出力からすべての行を読み取る
プロセスを起動し、その出力行を安全に読み取ります。
using System;
using System.Diagnostics;
using System.Threading.Tasks;
ProcessStartInfo startInfo = new("ping", ["127.0.0.1"])
{
RedirectStandardOutput = true,
RedirectStandardError = true
};
using Process process = Process.Start(startInfo)!;
// Read all output lines safely and asynchronously
await foreach (ProcessOutputLine line in process.ReadAllLinesAsync())
{
string prefix = line.StandardError ? "[Err]" : "[Out]";
Console.WriteLine($"{prefix} > {line.Content}");
}
4. デッドロックを起こさずに出力全体を読み取る(タプルを返す)
カスタムの ProcessStartInfo でプロセスを起動し、バッファーのデッドロックを起こさずに標準出力と標準エラーを分解したタプルに読み取ります。
using System;
using System.Diagnostics;
using System.Threading.Tasks;
ProcessStartInfo startInfo = new("git", ["diff"])
{
RedirectStandardOutput = true,
RedirectStandardError = true
};
using Process process = Process.Start(startInfo)!;
var (stdout, stderr) = await process.ReadAllTextAsync();
Console.WriteLine($"Diff output: {stdout}");
5. 起動して放置する(ファイア・アンド・フォーゲット)
システムのハンドル構造体を保持せずに、ヘルパー実行可能ファイルを起動します。
using System;
using System.Diagnostics;
// Fire and forget, getting back only the process ID
int pid = Process.StartAndForget("notepad.exe");
Console.WriteLine($"Notepad started with PID: {pid}");