Process.StandardOutput使用注意事项(转)
http://archive.cnblogs.com/a/1743707/
前段时间,经常使用C#调用控制台程序,便写了一个通用的方法,起初可以正常工作,直到遇到控制台程序输出内容较多时,发现控制台程序无法自动终止(任务管理器中始终有这个控制台进程,cpu使用率0),查阅msdn,才知道原来出现了死锁现象。
下面是最初的代码:
/// <summary>
/// common method to execute tool
/// </summary>
/// <param name="toolFile">tool's path</param>
/// <param name="args">arguments</param>
private static void ExecuteTool(string toolFile, string args)
{
Process p;
ProcessStartInfo psi;
psi = new ProcessStartInfo(toolFile);
psi.Arguments += args;
psi.UseShellExecute = false;
psi.RedirectStandardOutput = true; //允许重定向标准输出
psi.CreateNoWindow = true;
psi.RedirectStandardError = true;
psi.WindowStyle = ProcessWindowStyle.Hidden;
p = Process.Start(psi);
p.WaitForExit();
p.Close();
}
在读取StandardOutput流时,有两种方式,即同步和异步。
同步方式,会在读取流的一方和写入流的一方形成依赖关系,这种依赖关系形成了死锁的条件。当要写或读足够多的数据时,双方会等待对方写完或者读完,彼此的等待导致了死锁的产生。具体的解释可以参见msdn:http://msdn.microsoft.com/en-us/library/system.diagnostics.process.standardoutput(v=VS.80).aspx
如果我们不需要重定向输出,可以将psi.RedirectStandardOutput设置为false。
如果需要重定向输出,那么必须解决潜在的死锁问题,方法有两个:
方法一:Call ReadToEnd() before WaitForExit()
private static void ExecuteTool(string toolFile, string args)
{
Process p;
ProcessStartInfo psi;
psi = new ProcessStartInfo(toolFile);
psi.Arguments += args;
psi.UseShellExecute = false;
psi.RedirectStandardOutput = true; //允许重定向标准输出
psi.CreateNoWindow = true;
psi.RedirectStandardError = true;
psi.WindowStyle = ProcessWindowStyle.Hidden;
p = Process.Start(psi);
string output = p.StandardOutput.ReadToEnd(); //Call ReadToEnd() before WaitForExit()
p.WaitForExit();
p.Close();
}
方法二:采用异步方式(适用于同时获取标准输出流和错误流)
private static void ExecuteTool(string toolFile, string args)
{
Process p;
ProcessStartInfo psi;
psi = new ProcessStartInfo(toolFile);
psi.Arguments += args;
psi.UseShellExecute = false;
psi.RedirectStandardOutput = true; //允许重定向标准输出
psi.RedirectStandardError = true;
psi.CreateNoWindow = true;
psi.RedirectStandardError = true;
psi.WindowStyle = ProcessWindowStyle.Hidden;
p = Process.Start(psi);
p.OutputDataReceived += new DataReceivedEventHandler(OnDataReceived);
p.BeginOutputReadLine();
p.WaitForExit();
if (p.ExitCode != 0)
{
result.Append(p.StandardError.ReadToEnd());
}
p.Close();
}
private static void OnDataReceived(object Sender, DataReceivedEventArgs e)
{
if (e.Data != null)
{
result.Append(e.Data);
}
}
StandardOutput
当 Process 将文本写入其标准流中时,通常将在控制台上显示该文本。通过重定向 StandardOutput 流,可以操作或取消进程的输出。例如,您可以筛选文本、用不同方式将其格式化,也可以将输出同时写入控制台和指定的日志文件中。
若要使用 StandardOutput,您必须将 ProcessStartInfo.UseShellExecute 设置为 false,并且将ProcessStartInfo.RedirectStandardOutput 设置为 true。否则,读取 StandardOutput 流时将引发异常。
可以同步或异步读取重定向的 StandardOutput 流。Read、ReadLine 和 ReadToEnd 等方法对进程的输出流执行同步读取操作。这些同步读取操作只有在关联的 Process 写入其 StandardOutput 流或关闭该流后才能完成。
相反,BeginOutputReadLine 在 StandardOutput 流上开始异步读取操作。此方法会为流输出启用一个指定的事件处理程序并立即返回到调用方,这样当流输出被定向到该事件处理程序时,调用方还可以执行其他操作。
同步读取操作在读取 StandardOutput 流 的调用方及写入该流中的子进程之间引入一个依赖项。这些依赖项可能导致产生死锁情况。调用方读取子进程的重定向流时依赖于该子进程。调用方将等待读取操 作,直到子进程写入流或关闭流为止。子进程写入足够多的数据以填充重定向流的时间依赖于父进程。子进程将等待下一次写操作,直到父进程读取了全部流或关闭 该流为止。当调用方和子进程相互等待对方完成操作时,就会产生死锁情况,使双方都无法继续执行操作。您可以通过计算调用方和子进程之间的依赖项从而避免出 现死锁情况。
例如,下面的 C# 代码演示如何读取重定向流并等待子进程退出。
// Start the child process.
Process p = new Process();
// Redirect the output stream of the child process.
p.StartInfo.UseShellExecute = false;
p.StartInfo.RedirectStandardOutput = true;
p.StartInfo.FileName = "Write500Lines.exe";
p.Start();
// Do not wait for the child process to exit before
// reading to the end of its redirected stream.
// p.WaitForExit();
// Read the output stream first and then wait.
string output = p.StandardOutput.ReadToEnd();
p.WaitForExit();
该代码示例通过在 p.WaitForExit 之前调用 p.StandardOutput.ReadToEnd 避免产生死锁情况。如果父进程在 p.StandardOutput.ReadToEnd 之前调用p.WaitForExit,并且子进程写入足够多的文本以填充重定向流,就会产生死锁情况。父进程将无限期地等待子进程退出。子进程将无限期地等待父进程读取全部StandardOutput 流。
在读取标准输出和标准错误流的所有文本时,会出现类似的问题。例如,下面的 C# 代码对这两种流执行读取操作。
// Do not perform a synchronous read to the end of both
// redirected streams.
// string output = p.StandardOutput.ReadToEnd();
// string error = p.StandardError.ReadToEnd();
// p.WaitForExit();
// Use asynchronous read operations on at least one of the streams.
p.BeginOutputReadLine();
string error = p.StandardError.ReadToEnd();
p.WaitForExit();
此代码示例通过对 StandardOutput 流执行异步读取操作避免产生死锁情况。如果父进程在调用 p.StandardOutput.ReadToEnd 之后调用p.StandardError.ReadToEnd,并且子进程写入足够多的文本以填充错误流,就会产生死锁情况。父进程将无限期地等待子进程关闭其 StandardOutput 流。子进程将无限期地等待父进程读取全部 StandardError 流。
您可以使用异步读取操作避免出现这些依赖项及其潜在的死锁情况。或者,您还可以通过创建两个线程并读取每个线程中每个流的输出来避免产生死锁情况。
您不能对同一个重定向流混合使用异步和同步读取操作。在异步或同步模式下打开 Process 的重定向流后,对该流的所有进一步的读取操作都必须在同一模式下进行。例如,不要对 StandardOutput 流调用 BeginOutputReadLine 后接着调用 ReadLine,反之亦然。但是,您可以在不同的模式下读取两个不同的流。例如,您可以先调用 BeginOutputReadLine,然后再为 StandardError 流调用 ReadLine。
下面的示例生成一个新的用户定义的可执行文件,并读取它的标准输出。然后控制台上会显示输出。
Process myProcess = new Process();
ProcessStartInfo myProcessStartInfo = new ProcessStartInfo("Process_StandardOutput_Sample.exe" );
myProcessStartInfo.UseShellExecute = false;
myProcessStartInfo.RedirectStandardOutput = true;
myProcess.StartInfo = myProcessStartInfo;
myProcess.Start();
StreamReader myStreamReader = myProcess.StandardOutput;
// Read the standard output of the spawned process.
string myString = myStreamReader.ReadLine();
Console.WriteLine(myString);
myProcess.Close();