Process.BeginOutputReadLine/BeginErrorReadLineメソッドを使うと、標準出力・標準エラーに出力されるたびにイベントを発生させることができ、イベントハンドラによって非同期的に読み込むことができます。

このメソッドを呼び出すと、標準出力・標準エラーに書き込まれた内容はOutputDataReceivedイベント/ErrorDataReceivedイベントとして通知されます。 このイベントは、開始メソッドの名称Begin*ReadLineが示すように、標準出力・標準エラーから1行読み込まれるごとに(改行文字が出力されるたびに)発生します。 また、Processクラスのイベントであるため、ProcessStartInfoクラスで起動オプションと同時にイベントハンドラを設定することはできません。

イベントハンドラとなるDataReceivedEventHandlerデリゲート型では、DataReceivedEventArgs.Dataプロパティを参照することにより、標準出力・標準エラーに出力された内容を得ることができます。 イベントは1行ごとに発生するため、DataReceivedEventArgs.Dataプロパティからは改行文字を除いた1行分の内容が取得されます。

子プロセスが終了して標準出力・標準エラーが閉じられた場合は、Dataプロパティにnull/Nothingが設定された状態でイベントが発生するため、これを元にデータの終端あるいは子プロセスの終了を検知することができます。 なお、イベントハンドラの引数senderには子プロセスのProcessが渡されます。

BeginOutputReadLine/BeginErrorReadLineを使った非同期読み込みは、同期的に読み込む場合と同様にProcessStartInfo.RedirectStandardOutput/ RedirectStandardErrortrueを指定して子プロセスを起動します。 また、子プロセスを起動したあと、読み込みを開始する時点でProcess.BeginOutputReadLine/BeginErrorReadLineメソッドを呼び出します。

一度非同期的な読み込みを開始すると、以降はStandardOutput/StandardErrorを使った同期的な読み込みは例外InvalidOperationExceptionをスローするようになり、一切できなくなります。

parent.exe:子プロセスの標準出力・標準エラーを非同期的に読み込む
using System;
using System.Diagnostics;

class Sample {
  static void Main()
  {
    // 子プロセスを起動するためのProcessを作成
    using (var child = new Process()) {
      // child.exeを子プロセスとして起動する
      child.StartInfo.FileName = "child.exe";

      // シェルを使用せず子プロセスを起動する
      // (リダイレクトするために必要)
      // ⚠.NET Core/.NET 5以降ではデフォルトでfalseとなっている
      child.StartInfo.UseShellExecute = false;

      // 起動した子プロセスの標準出力・標準エラーをリダイレクトする
      child.StartInfo.RedirectStandardOutput = true;
      child.StartInfo.RedirectStandardError = true;

      // リダイレクトした標準出力の内容を受信するイベントハンドラを設定する
      child.OutputDataReceived += (sender, e) => {
        // 子プロセスの標準出力から受信した内容を自プロセスの標準出力に書き込む
        Console.WriteLine($"<stdout> {(e.Data ?? "(end of stream)")}");
      };
      // リダイレクトした標準エラーの内容を受信するイベントハンドラを設定する
      child.ErrorDataReceived += (sender, e) => {
        // 子プロセスの標準エラーから受信した内容を自プロセスの標準エラーに書き込む
        Console.WriteLine($"<stderr> {(e.Data ?? "(end of stream)")}");
      };

      // プロセスを起動する
      child.Start();

      // 標準出力・標準エラーの非同期読み込みを開始する
      child.BeginOutputReadLine();
      child.BeginErrorReadLine();

      // 子プロセスの終了を最大250ミリ秒待機する
      while (!child.WaitForExit(250)) {
        // 終了していない場合は、待機中のメッセージを表示する
        Console.WriteLine("(waiting)");
      }
    }
  }
}
child.exe:一定時間おきに標準出力と標準エラーに出力する
using System;

class Sample {
  static void Main()
  {
    // 100ミリ秒おきに、標準出力と標準エラーに出力する
    for (var i = 0; i < 6; i++) {
      if (i % 2 == 0)
        Console.Error.WriteLine("Hello, stderr!");
      else
        Console.WriteLine("Hello, stdout!");

      System.Threading.Thread.Sleep(100);
    }
  }
}
実行結果
>parent.exe
<stderr> Hello, stderr!
<stdout> Hello, stdout!
<stderr> Hello, stderr!
(waiting)
<stdout> Hello, stdout!
<stderr> Hello, stderr!
(waiting)
<stdout> Hello, stdout!
<stdout> (end of stream)
<stderr> (end of stream)

一度開始した非同期的に読み込みは、CancelOutputRead/CancelErrorReadメソッドを呼び出すことで中止することができます。 また、中止したあとに再度BeginOutputReadLine/BeginErrorReadLineメソッドを呼び出すことで読み込みを再開することもできます。

CancelOutputRead/CancelErrorReadメソッドを呼び出すとイベントの発生が中止され、再度BeginOutputReadLine/BeginErrorReadLineメソッドを呼び出すとイベントの発生が再開されます。 ただし、中断している間も標準出力・標準エラーの非同期的な読み込み自体は継続して行われているため、再開後は再開以降に標準出力・標準エラーへ書き込まれた分のイベントが発生します。 (再開までの間に書き込まれた分のイベントは発生しない)