コンソールアプリの、標準出力/標準エラー出力への出力を受け取る(非同期版)

もくじ
https://tera1707.com/entry/2022/02/06/144447

やりたいこと

以前の記事で、コンソールアプリの標準出力を受け取る方法を調べた。

今回は、それを非同期で出来るというのを見たので、やり方をメモしておく。

サンプルコード

実験用に、標準出力と標準エラー出力に、100msおきに出力を行う子プロセスと、 それを呼び出して出力を受け取る親プロセスを作ってみた。

親プロセス(要点)

子プロセスを呼び出して、そこから出力される標準出力、標準エラー出力を受け取る。

using System.Diagnostics;

namespace ConsoleApp1
{
    internal class Program
    {
        static void Main(string[] args)
        {
            var si = new ProcessStartInfo()
            {
                // 実行するコマンド(プロセス名)
                FileName = @"TestProcess.exe", 
                // 渡す引数
                Arguments = "a.txt",
                // ウインドウ無しならtrue
                CreateNoWindow = true,
                // 標準出力をこのアプリで受け取るよう指定
                RedirectStandardOutput = true,
                // 標準エラー出力をこのアプリで受け取るよう指定
                RedirectStandardError = true,
                UseShellExecute = false,
            };
            
            using (var proc = new Process())
            {
                proc.EnableRaisingEvents = true;
                proc.StartInfo = si;

                proc.OutputDataReceived += (sender, ev) =>
                {
                    if (ev.Data is not null)
                        Debug.WriteLine($"標準出力 {ev.Data}");
                    else
                        Debug.WriteLine($"標準出力 ev.Data がnull");
                };
                proc.ErrorDataReceived += (sender, ev) =>
                {
                    if (ev.Data is not null)
                        Debug.WriteLine($"標準エラー出力 {ev.Data}");
                    else
                        Debug.WriteLine($"標準エラー出力 ev.Data がnull");
                };
                proc.Exited += (sender, ev) =>
                {
                    Debug.WriteLine($"終了イベント到来");
                };

                // プロセス起動
                proc.Start();
                
                // 非同期出力読出し開始
                proc.BeginErrorReadLine();
                proc.BeginOutputReadLine();

                // 終了まで(同期的に)待つ
                proc.WaitForExit();
            }

            Debug.WriteLine("プログラム終了");
        }
    }
}

子プロセス(親から呼ばれるプロセス(TestProcess.exe))

100msおきに標準出力に数字を出力し、 その後100msおきに標準エラー出力に数字を出力するプログラム。

namespace TestProcess
{
    internal class Program
    {
        static void Main(string[] args)
        {
            for (int i = 0; i < 5; i++)
            {
                Console.WriteLine(i.ToString());
                Thread.Sleep(100);
            }

            for (int i = 0; i < 5; i++)
            {
                Console.Error.WriteLine(i.ToString());
                Thread.Sleep(100);
            }
        }
    }
}

実行結果

標準出力 0
標準出力 1
標準出力 2
標準出力 3
標準出力 4
標準エラー出力 0
標準エラー出力 1
標準エラー出力 2
標準エラー出力 3
標準エラー出力 4
標準出力 ev.Data がnull
標準エラー出力 ev.Data がnull
終了イベント到来
プログラム終了

※終了時、最後にnullが送られてくる?実験用子プロセスのプログラムの作りが悪いだけかも?

参考

EnableRaisingEvents フラグ

プロセスが終了したときに、Exited イベントを発生させるかどうかのフラグ。

https://learn.microsoft.com/ja-jp/dotnet/api/system.diagnostics.process.enableraisingevents?view=net-7.0

非同期外部プロセス起動で標準出力を受け取る際の注意点

このぺージのサンプルでは、CancellationTokenのCancel待ちをして、プロセス終了を待っていたが、今回はProcessをproc.WaitForExit();する形にしてみた。試した限り、どちらでも同じ挙動になっていた。

https://qiita.com/skitoy4321/items/10c47eea93e5c6145d48