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:子プロセスの標準出力・標準エラーを非同期的に読み込む
Option Infer On

Imports System
Imports System.Diagnostics
Imports System.IO

Class Sample
  Shared Sub Main()
    ' 子プロセスを起動するためのProcessを作成
    Using child As 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

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

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

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

      ' 子プロセスの終了を待機する
      Do Until child.WaitForExit(250)
        ' 終了するまでの間、250ミリ秒毎に待機中のメッセージを表示する
        Console.WriteLine("(waiting)")
      Loop
    End Using
  End Sub
End Class
child.exe:一定時間おきに標準出力と標準エラーに出力する
Imports System

Class Sample
  Shared Sub Main()
    ' 100ミリ秒おきに、標準出力と標準エラーに出力する
    For i As Integer = 0 To 5
      If i Mod 2 = 0 Then
        Console.Error.WriteLine("Hello, stderr!")
      Else
        Console.WriteLine("Hello, stdout!")
      End If

      System.Threading.Thread.Sleep(100)
    Next
  End Sub
End Class
実行結果
>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メソッドを呼び出すとイベントの発生が再開されます。 ただし、中断している間も標準出力・標準エラーの非同期的な読み込み自体は継続して行われているため、再開後は再開以降に標準出力・標準エラーへ書き込まれた分のイベントが発生します。 (再開までの間に書き込まれた分のイベントは発生しない)