之前學過透過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
{
staticint 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;
}
publicstaticint 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;
}
}
}