[C#] 使用Process.StandardOutput讀取輸出資料時, 需注意語法執行先後順序

[C#] 使用Process.StandardOutput讀取輸出資料時, 需注意語法執行先後順序

前言

最近在工作上碰到一個有趣的Bug,使用情境是這樣子的,

我有一隻Powershell會根據資料庫所撈出的結果,

發送Email給不同的使用者,根據結果不同,發送的對象也不同,

而為了除錯方便,所以也會直接在Console上輸出訊息。

然後剛好又碰到另外一個需求是要再C#之中使用Process來執行這隻程式,

但為了能夠追蹤程式執行時期是否有問題,

所以我會設定Process.StartInfo.RedirectStandardOutput = true來將Console上的訊息輸出成文字檔,

在資料量小的時候一切都是正常的,

但最近剛好碰到資料量比較大的狀況時,會發現只發送了一半的Email程式就停止了,

真是百思而不得其解!

 

實際演練

現在重新來模擬一下案發現場,

如果沒透過C#,直接執行該Powershell的話,我可以在Console上面看到Powershell執行的狀況

2011-08-02_195531

而為了透過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);     

在使用者少的時候是完全沒有問題的,輸出的文字檔長得像這樣

2011-08-02_195959

但最近資料量比較大,卻發現程式常常Email發到一半就停住了,卻一直找不到原因,

後來才發現,原來問題出在並沒有依照MSDN的程式撰寫建議來使用Process.StandardOutput。

MSDN的建議是,Process.StandardOutput.ReadToEnd應該放在Process.WaitForExit()前執行,

或是使用非同步的方法來讀取輸出的資料流。

而為什麼這邊會停住呢? 其實是這樣的

2011-08-02_201514

 

Console程式先執行的WaitForExit(),等待Powershell執行完畢,

而Powershell中所輸出到畫面的資料,都會被寫到StandardOutput的Buffer中,

在資料量小的時候是沒什麼問題的,執行完畢就會回應WaitForExit(),

C#可接著讀取StandardOutputk的Buffer中的資料並寫入檔案,

但是!!!  StandardOutput的Buffer其實是有容量上限的!

若在畫面輸出的資料量大的時候,Powershell畫面所輸出的資料是會將Buffer寫滿的,

而這時候就必須等候Console將Buffer中的資料讀出才會繼續往下執行,

但這時候Console不會去執行這個動作,因為他正在WaitForExit()方法中等候Powershell的回應,

這就造成一個Deadlock了!

 

我們重新來看看MSDN上的建議寫法

2011-08-02_202448

果然,重新調整語法的先後順序後,程式都可以正常執行了

 

結語

如果在使用不熟悉的語法時,還是要仔細的確認過MSDN上的建議來寫程式,

不要僅憑著使用過的記憶來寫,很有可能會碰到無法預期的錯誤,

而在錯誤發生之後,也要儘量的去瞭解錯誤發生的原因,

並且將他按照正確的做法修正,也可以避免未來再度發生同樣的問題,

在這邊也要特別感謝Louis Wang老師給予協助指導,才能順利解決這個問題,

也歡迎大家多多指教討論囉 :)

 

P.S. 若同時使用StandardOutput和StandardError時,建議使用非同步方法處理,

      以避免Lock產生,可以參考 Process.StandardError Property

 

參考

  1. Process Class
  2. Process.StandardOutput Property
  3. Process.OutputDataReceived Event
  4. DEADLOCK WHEN CAPTURING STANDARD OUTPUT