經??吹?STRONG>論壇中有人問到當用Process組件啟動新的進程后,如何獲取它的輸出的問題。采取將子進程的輸出定向到一個臨時文件中,當然也可以解決問題。但是這樣每次父進程從臨時文件中獲取信息后,還要刪除該臨時文件,畢竟比較麻煩。其實,Process提供了幾個屬性可以獲取輸出。在.net框架sdk的幫助文檔里面就有這方面的例子,但是對于如何同時獲取錯誤輸出和標準輸出方面沒有給出具體代碼,本文將給出實例并對管道的特性作一些說明。
一、獲取子進程標準輸出和錯誤輸出的的方法:
我們寫一個小程序p2.cs,用它來產生標準輸出和錯誤輸出。
//p2.cs代碼如下:
using System;
class class1
{
public static void Main()
{
int i = 0;
string s1 = String.Format("out:{0,4}--------------------------------------------------",i);
System.Console.Out.WriteLine(s1);
string s2 = String.Format("err:{0,4}**************************************************",i);
System.Console.Error.WriteLine(s2);
}
}
編譯成p2.exe
獲取子進程的標準輸出和錯誤輸出的源程序
//p1.cs
....
Process p = new Process("p2.exe");
p.StartInfo.UseShellExecute = false; //指定不使用系統的外殼程序,而是直接啟動被調用程序本身
p.StartInfo.RedirectStandardOutput = true; //只有將此屬性設為true,才能通過管道獲取子進程輸出
p.StartInfo.RedirectStandardError = true;
p.Start(); //啟動子進程
string output = p.StandardOutput.ReadToEnd(); //讀取標準輸出
string error = p.StandardError.ReadToEnd(); //讀取錯誤輸出
p.WaitForExit(); //等待子進程執行完畢
...
上例中,父進程啟動子進程后,就等待著從管道中取走標準輸出,取走全部標準輸出后取走錯誤輸出。我們運行起來沒有任何錯誤。但是一旦我門增加p2.exe中的標準輸出和錯誤輸出的字節數:
for(i=0;i<200;i++)
System.Console.Out.WriteLine(s1);
for(i=0;i<200;i++)
System.Console.Error.WriteLine(s2);
編譯后,再運行,就會發現父進程和子進程出現了死鎖,只有強行關閉才能終止進程。
二、管道:
Process 組件通過管道與子進程進行通訊。如果同時重定向標準輸出和標準錯誤,然后試圖讀取它們,當管道被填滿時候就會出現問題。上例中,父進程只有讀完了所有的標準輸出才能讀錯誤輸出,而子進程的標準輸出每當把管道填滿時候,父進程都會取走,子進程接著向管道輸出后續的標準輸出。這都運行的很好??墒钱斪舆M程執行到輸出錯誤輸出的時候,由于子進程還沒有運行結束,它的標準輸出流就不會處于結束狀態(雖然在我們的例子中它的200次循環的標準輸出已經執行完畢),這就導致父進程中的ReadToEnd()方法不會執行完畢,所以父進程中的p.StandardError.ReadToEnd()無法執行,從而管道中的錯誤輸出無法被讀取,子進程的后續錯誤輸出無法寫入管道,最終子進程和父進程彼此無限等待,出現了阻塞情況。
子進程通過檢查管道中最后一個字節是否被取走,來決定是否輸出后續內容。也就是說,如果管道緩沖區中最后一個字節之前的所有內容都被取走,子進程仍然會處于等待中;如果前面所有字節都沒有取走,但最后一個字節內容被取走,子進程會繼續向管道里輸出后續內容,當然這樣只能輸出一字節到管道中最后位置。
我們如果把p1.cs中的string output = p.StandardOutput.ReadToEnd()改為:
for(int i=0;i<200*60;i++)
Console.Write((char) p.StandardOutput.Read());
就不會出現死鎖情況。因為當獲取標準輸出的循環執行完后,會接著執行獲取錯誤輸出的語句,而不需等待標準輸出流的結束。但是這種方法毫無實際意義,因為實際中,我們并不可能像本例中知道標準輸出會有多少字節。
三、用多線程分別獲取標準輸出和錯誤輸出
對于這種情況,.net框架文檔中建議這樣解決:創建兩個線程,以便應用程序可以在單獨的線程上讀取每個流的輸出。下面給出例子:
//p1.cs:
...
class class1
{
.....
public static void Main()
{
ProcessStartInfo pi = new ProcessStartInfo();
pi.FileName = @"c:\p2.exe";
pi.UseShellExecute = false;
pi.RedirectStandardOutput = true;
pi.RedirectStandardError = true;
System.Diagnostics.Process p = new Process();
p.StartInfo = pi;
p.Start();
tout t1 = new tout(p);
terr t2 = new terr(p);
Thread thread1 = new Thread(new ThreadStart(t1.Read));
Thread thread2 = new Thread(new ThreadStart(t2.Read));
thread1.Start();
thread2.Start();
p.WaitForExit();
Console.WriteLine("p2.exe結束");
}
}
class tout
{
Process p;
public tout(Process p)
{
this.p = p;
}
public void Read()
{
int a = -1;
while((a = p.StandardOutput.Read()) > 0)
{
Console.Write( ((char) a).ToString() );
}
Thread.CurrentThread.Abort();
return;
}
}
class terr
{
Process p;
public terr(Process p)
{
this.p = p;
}
public void Read()
{
int a = -1;
while((a = p.StandardError.Read()) > 0)
{
Console.Write(((char) a).ToString());
}
Thread.CurrentThread.Abort();
return;
}
}