使用Process類重定向輸出與錯誤時遇到的問題 (轉)


 

 

程序中要調用外部程序cmd.exe執行一些命令行,並取得屏幕輸出,使用了Process類,基本代碼如下:

Process process = new Process(); process.StartInfo.FileName = "cmd.exe"; process.StartInfo.UseShellExecute = false; process.StartInfo.RedirectStandardInput = true; process.StartInfo.RedirectStandardOutput = true; process.StartInfo.RedirectStandardError = true; process.StartInfo.CreateNoWindow = true; process.Start();
………… //一些處理
process.StandardInput.WriteLine("exit"); //取輸出內容 String outputString = process.StandardOutput.ReadToEnd(); process.WaitForExit(); process.Close();

實際使用中發現有時程序會無響應,並且是在執行某些特定的命令時才會無響應。跟蹤調試發現,問題出在process.StandardOutput.ReadToEnd()上,在這一行按F10,程序不向下執行,也不產生Exception。

[解決過程]

由於調試時程序不向下執行,猜測ReadToEnd()沒有返回,被阻塞住了,查了查MSDN,沒有看到有相關內容。又反編了一下mscorlib.dll,看了看StreamReader.ReadToEnd()的實現方法,是這么一段代碼:

public override string ReadToEnd() {     int num1;     if (this.stream == null)     {         __Error.ReaderClosed();     }     char[] chArray1 = new char[this.charBuffer.Length];     StringBuilder builder1 = new StringBuilder(this.charBuffer.Length);     while ((num1 = this.Read(chArray1, 0, chArray1.Length)) != 0)     {         builder1.Append(chArray1, 0, num1);     }     return builder1.ToString(); }

    從中看出ReadToEnd()是利用Read()方法實現的,又去MSDN看了,仍然沒有提到Read()是阻塞函數,於是只能上網去查一查相關資料,有人提到了類似的問題,並且給出了解決方法:使用兩個線程分別做StandardOutput.ReadToEnd()和StandardError.ReadToEnd()。試了一下,果然可以解決。

[后續思考]

問題雖然解決,不過原理沒有弄明白,還要繼續深入。查閱相關資料,大致了解了Process的運作模式,它以子進程的形式啟動cmd.exe,如果指定了重定向Input、Output或Error,就建立相應的管道在父子進程之間進行通訊。

基於這些內容以及關於管道的理解,得到以下幾條猜測:

1、input、output、error的三條管道是相互獨立的。

2、管道有大小,空時不可讀,滿時不可寫。

3、遇到管道不可讀寫時,相應的進程會阻塞等待可讀寫為止。

4、子進程結束前,output流與error流不會結束。

這樣再翻回來考慮之前遇到的問題,可以猜測無響應時是這樣一種情況:

建立Process時指定了Output和Error都重定向,父子進程間就建立了2條管道,cmd.exe不停輸出output與error,分別進入兩條管道,對output,管道滿后,子進程會停下來等待父進程取走數據,父進程的StandardOutput.ReadToEnd()方法正是取數據的,它在管道空時會處於阻塞狀態,有數據時就取走,這樣子進程會繼續寫output。

但是對於error,父進程沒有相應的ReadToEnd()方法,很快error的管道就滿了,由於無法寫error,子進程會停下來等待,但是父進程永遠不讀,子進程也就永遠不再繼續,形成死鎖。

對於不出問題的情況,一定是error管道根本就沒有寫滿,這也就解釋了為什么命令的內容決定了是否會出現無響應的情況。

那么直接在StandardOutput.ReadToEnd()后面加一條StandardError.ReadToEnd()行不行呢?答案是不行的,基於上面的猜測與分析,ReadToEnd()方法是阻塞的,在子進程結束前,output流不會關閉,所以StandardOutput.ReadToEnd()會一直等待,造成后面的StandardError.ReadToEnd()方法根本不被執行,還是會產生死鎖。所以解決方案里才提到了要建立2個線程,讓兩個管道的ReadToEnd()方法獨立執行。

再回來考慮我的程序中的情況,之所以沒有去讀Error,是因為我根本不關心子進程產生的error輸出,所以應該有更簡單的方法,就是把process.StartInfo.RedirectStandardError置為false,經測試驗證,想法是對的, 這樣還避免了使用線程。

在分析的過程中還對process.WaitForExit()方法產生了興趣,從msdn的描述來看,它的作用是等待子進程結束,應該也是阻塞的,但在我的程序中它在ReadToEnd()方法后面,是否還有實際作用呢?猜測子進程結束后,ReadToEnd()方法才返回,WaitForExit()方法才被調用,但此時它已沒有什么用了,肯定立刻返回,向下執行。跟蹤調試了一下,驗證了自己的想法,於是干脆去掉了這一句,經驗證並沒有對運行產生影響。

以上很大部分都是自己的猜測,並不敢保證准確,也許很多地方理解有誤,歡迎各位大牛指正。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM