[C#] 使用Process.StandardOutput讀取輸出資料時, 需注意語法執行先後順序
前言
最近在工作上碰到一個有趣的Bug,使用情境是這樣子的,
我有一隻Powershell會根據資料庫所撈出的結果,
發送Email給不同的使用者,根據結果不同,發送的對象也不同,
而為了除錯方便,所以也會直接在Console上輸出訊息。
然後剛好又碰到另外一個需求是要再C#之中使用Process來執行這隻程式,
但為了能夠追蹤程式執行時期是否有問題,
所以我會設定Process.StartInfo.RedirectStandardOutput = true來將Console上的訊息輸出成文字檔,
在資料量小的時候一切都是正常的,
但最近剛好碰到資料量比較大的狀況時,會發現只發送了一半的Email程式就停止了,
真是百思而不得其解!
實際演練
現在重新來模擬一下案發現場,
如果沒透過C#,直接執行該Powershell的話,我可以在Console上面看到Powershell執行的狀況
而為了透過C#執行這隻Powershell,所以我使用了Process類別來啟動程式,
並且透過讀取Process.StandardOutput的資料流來將畫面結果輸出成文字檔
Process process = new Process();
process.StartInfo.FileName = @".\EmailToEveryBody.exe";
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardOutput = true;
process.Start();
process.WaitForExit();
var result = process.StandardOutput.ReadToEnd();
string logPath = string.Format(@".\EmailLog_{0:yyyyMMddhhmmssfff}.txt", DateTime.Now);
File.WriteAllText(logPath,result);
在使用者少的時候是完全沒有問題的,輸出的文字檔長得像這樣
但最近資料量比較大,卻發現程式常常Email發到一半就停住了,卻一直找不到原因,
後來才發現,原來問題出在並沒有依照MSDN的程式撰寫建議來使用Process.StandardOutput。
MSDN的建議是,Process.StandardOutput.ReadToEnd應該放在Process.WaitForExit()前執行,
或是使用非同步的方法來讀取輸出的資料流。
而為什麼這邊會停住呢? 其實是這樣的
Console程式先執行的WaitForExit(),等待Powershell執行完畢,
而Powershell中所輸出到畫面的資料,都會被寫到StandardOutput的Buffer中,
在資料量小的時候是沒什麼問題的,執行完畢就會回應WaitForExit(),
C#可接著讀取StandardOutputk的Buffer中的資料並寫入檔案,
但是!!! StandardOutput的Buffer其實是有容量上限的!
若在畫面輸出的資料量大的時候,Powershell畫面所輸出的資料是會將Buffer寫滿的,
而這時候就必須等候Console將Buffer中的資料讀出才會繼續往下執行,
但這時候Console不會去執行這個動作,因為他正在WaitForExit()方法中等候Powershell的回應,
這就造成一個Deadlock了!
我們重新來看看MSDN上的建議寫法
果然,重新調整語法的先後順序後,程式都可以正常執行了
結語
如果在使用不熟悉的語法時,還是要仔細的確認過MSDN上的建議來寫程式,
不要僅憑著使用過的記憶來寫,很有可能會碰到無法預期的錯誤,
而在錯誤發生之後,也要儘量的去瞭解錯誤發生的原因,
並且將他按照正確的做法修正,也可以避免未來再度發生同樣的問題,
在這邊也要特別感謝Louis Wang老師給予協助指導,才能順利解決這個問題,
也歡迎大家多多指教討論囉 :)
P.S. 若同時使用StandardOutput和StandardError時,建議使用非同步方法處理,
以避免Lock產生,可以參考 Process.StandardError Property
參考