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をスローするようになり、一切できなくなります。

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

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

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.OutputDataReceived += (sender, e) => {
        // 子プロセスの標準出力から受信した内容を自プロセスの標準出力に書き込む
        Console.WriteLine($"<stdout> {(e.Data ?? "(end of stream)")}");
      };

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

      // 標準出力の非同期読み込みを開始する
      child.BeginOutputReadLine();
      Console.WriteLine("(begin)");

      // 500ミリ秒待機する
      System.Threading.Thread.Sleep(500);

      // 標準出力の非同期読み込みを中断する
      child.CancelOutputRead();
      Console.WriteLine("(cancel)");

      // 500ミリ秒待機する (この間も標準出力の非同期読み込みは継続する)
      System.Threading.Thread.Sleep(500);

      // 標準出力の非同期読み込みを再度開始する
      child.BeginOutputReadLine();
      Console.WriteLine("(begin)");

      // 子プロセスの終了を待機する
      child.WaitForExit();
    }
  }
}
child.exe:一定時間おきに標準出力に出力する
using System;

class Sample {
  static void Main()
  {
    // 100ミリ秒おきにカウントアップして標準出力に出力する
    for (var i = 0; i < 15; i++) {
      Console.WriteLine(i);

      System.Threading.Thread.Sleep(100);
    }
  }
}
実行結果
>parent.exe
(begin)
<stdout> 0
<stdout> 1
<stdout> 2
<stdout> 3
<stdout> 4
(cancel)
(begin)
<stdout> 10
<stdout> 11
<stdout> 12
<stdout> 13
<stdout> 14
<stdout> (end of stream)