Process.WaitForExit()でハングしないようにするには

  • 15
    Like
  • 0
    Comment
More than 1 year has passed since last update.

コード内で別プロセスを起動する際にはProcessクラスを使いますが、出力をリダイレクトする場合は正しく初期化しないとWaitForExit()がいつまで待っても返って来ません。

リダイレクションのための内部バッファは4Kバイトしかありません。もし呼び出したプロセスが4KB以上出力した場合、その出力が取り出されるまでプロセスが停止するためです。UNIXのパイプの扱いと同じですね。この問題を避ける一番簡単な方法は、簡単なイベントハンドラを用意して適宜データを吸い出してやることです。

以下にpowershellを内部から呼び出すサンプルを書きます。

var command = "powershell.exe";
var arguments = "gci $ENV:TEMP";
var startInfo = new ProcessStartInfo(command, arguments) {
    WorkingDirectory = Environment.CurrentDirectory,
    RedirectStandardOutput = true,
    RedirectStandardError = true,
    UseShellExecute = false,
    CreateNoWindow = true
};

var output = new StringBuilder();
var timeout = TimeSpan.FromMinutes(2); // 2分だけ待つ

using (var process = Process.Start(startInfo))
{
    var stdout = new StringBuilder();
    var stderr = new StringBuilder();

    process.OutputDataReceived += (sender, e) => { if (e.Data != null) { stdout.AppendLine(e.Data); } }; // 標準出力に書き込まれた文字列を取り出す
    process.ErrorDataReceived += (sender, e) => { if (e.Data != null) { stderr.AppendLine(e.Data); } }; // 標準エラー出力に書き込まれた文字列を取り出す
    process.BeginOutputReadLine();
    process.BeginErrorReadLine();

    var isTimedOut = false;

    if (!process.WaitForExit((int)timeout.TotalMilliseconds))
    {
        isTimedOut = true;
        process.Kill();
    }
    process.CancelOutputRead();
    process.CancelErrorRead();

    output.AppendLine(stdout.ToString());
    output.AppendLine(stderr.ToString());
    if (isTimedOut)
        output.AppendLine("TIMEOUT AT " + DateTimeOffset.Now);
}
var resultString = output.ToString();  // これを使う

適当にメソッドにするなりヘルパークラスに入れるなりしてお使いください。タイムアウト発生時、上記サンプルでは文字列に追加していますが、TimeoutExceptionを投げるのもありです。まあお好みで。