之前學過透過RedirectStandardOutput設定,可在.NET呼叫其他命令列程式並接收其顯示內容的技巧。這回則有額外需求,由於某個命令列轉檔工具執行耗時(可能長達數分鐘),故進行期間會持續輸出進度資訊讓使用者安心,但依以前StandardOutput.ReadToEnd()的做法,.NET呼叫端只能在數分鐘後一次取得全部顯示結果,無法即時掌握處理進度,使用者體驗大大扣分。

研究之後,發現貼心的.NET BCL早有因應對策: OutputDataReceived!

做法是先在Process.OutputDataReceived事件上加入處理邏輯,接著呼叫Process.BeginOutputReadLine(),之後外部程式每輸出一行內容,OutputDataReceived事件便被觸發一次,並可由DataReceivedEventArgs.Data取得輸出內容進行自訂處理。

以下的程式範例,一人分飾兩角,展示了OutputDataReceived、ErrorDataReceived、從Output及Error輸出內容、傳回ExitCode等小技巧:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
 
namespace TestProcOutput
{
    class Program
    {
        static int Main(string[] args)
        {
            //模擬Command Line Utility程式行為
            if (args.Length == 1)
            {
                switch (args[0])
                {
                    case "simu":
                        //間隔一秒由正常Output輸出0-4
                        for (int i = 0; i < 5; i++)
                        {
                            Console.WriteLine(i);
                            Thread.Sleep(1000);
                        }
                        //正常結束ExitCode = 0
                        return 0;
                    default:
                        //透過Error TextWriter輸出訊息
                        Console.Error.WriteLine("Syntax Error!");
                        //傳回ExtiCode = 1
                        return 1;
                }
            }
            //宣告兩個Callback分別顯示Output及Error接收到的內容
            Action<string> outcb = (s) =>
            {
                Console.WriteLine("OUT:" + s);
            };
            Action<string> errcb = (s) =>
            {
                Console.WriteLine("ERR:" + s);
            };
            //第一次測試由Output輸出
            int exitCode = Execute("TestProcOutput", "simu", outcb, errcb);
            Console.WriteLine("ExitCode={0}", exitCode);
            //第二次則測試由Error輸出
            exitCode = Execute("TestProcOutput", "boo", outcb, errcb);
            Console.WriteLine("ExitCode={0}", exitCode);
            Console.Read();
            return 0;
        }
 
        public static int Execute(string exeFile, string argument,
            Action<string> outputCallback, Action<string> errorCallback)
        {
            ProcessStartInfo si = new ProcessStartInfo()
            {
                FileName = exeFile,
                Arguments = argument,
                //必須要設定以下兩個屬性才可將輸出結果導向
                UseShellExecute = false,
                RedirectStandardOutput = true,
                RedirectStandardError = true,
                //不顯示任何視窗
                CreateNoWindow = true
            };
            Process p = new Process()
            {
                StartInfo = si,
                EnableRaisingEvents = true,
            };
            //開始執行
            p.Start();
            //透過OutputDataReceived及ErrorDataReceived即時接收輸出內容
            p.OutputDataReceived +=
                (o, e) =>
                {
                    if (!string.IsNullOrEmpty(e.Data) && outputCallback != null)
                    {
                        outputCallback(e.Data);
                    }
                };
            p.ErrorDataReceived +=
                (o, e) =>
                {
                    if (!string.IsNullOrEmpty(e.Data) && errorCallback != null)
                    {
                        errorCallback(e.Data);
                    }
                };
            //呼叫Begin*ReadLine()開始接收輸出結果
            p.BeginOutputReadLine();
            p.BeginErrorReadLine();
 
            p.WaitForExit();
            return p.ExitCode;
        }
    }
}

Comments

Be the first to post a comment

Post a comment