前段时间,经常使用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);
}
}