经常看到论坛中有人问到当用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 组件通过管道与子进程进行通讯。如果同时重定向标准输出和标准错误,然后试图读取它们,当管道被填满时候就会出现问题。上例中,父进程只有读完了所有的标准输出才能读错误输出,而子进程的标准输出每当把管道填满时候,父进程都会取走,子进程接着向管道输出后续的标准输出。这都运行的很好。可是当子进程执行到输出错误输出的时候,由于子进程还没有运行结束,它的标准输出流就不会处于结束状态(虽然在我们的例子中它的200次循环的标准输出已经执行完毕),这就导致父进程中的ReadToEnd()方法不会执行完毕,所以父进程中的p.StandardError.ReadToEnd()无法执行,从而管道中的错误输出无法被读取,子进程的后续错误输出无法写入管道,最终子进程和父进程相互无限等待,出现了堵塞情况。 来源:www.va1314.com/bc 子进程通过检查管道中最后一个字节能否被取走,来决定能否输出后续内容。也就是说,如果管道缓冲区中最后一个字节之前的所有内容都被取走,子进程仍然会处于等待中;如果前面所有字节都没有取走,但最后一个字节内容被取走,子进程会继续向管道里输出后续内容,当然这样只能输出一字节到管道中最后位置。 我们如果把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; } } |