最近做项目发现创建Process调用外部程序时, 当处理的数据量变大后,Process无法退出,主程序卡死。
原代码:
Using (Process process = new Process())
{
process.StartInfo = new ProcessStartInfo(executablePath, args);
if (workingFolder != null)
{
process.StartInfo.WorkingDirectory = workingFolder;
}
process.StartInfo.CreateNoWindow = true;
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.RedirectStandardError = true;
DateTime start = DateTime.Now;
process.Start();
process.WaitForExit();
DateTime finish = DateTime.Now;
string processOutput = process.StandardError.ReadToEnd() +
process.StandardOutput.ReadToEnd();
}
这段代码的作用是调用一个外部程序向一个服务器上传一串数据,原来数据大小是128bit, 现在是4K bit。
尝试了各种方式,在网上搜集各类资料,查找可能卡顿的地方,本来以为是调用的外部程序的问题,最后发现是主程序的问题。
原因分析:
1. 使用同步方式读取执行结果,在StandardOutput.ReadToEnd之前调用了WaitforExit,导致死锁。
原代码使用了同步读取执行结果的方式,在StandardOutput.ReadToEnd之前调用了WaitforExit。当调用方从子进程的重定向流中进行读取时,它依赖于子进程。 调用方等待读取操作,直到子级写入流或关闭流为止。 当子进程写入足够的数据以填充其重定向流时,它依赖于父进程。 子进程将在下一次写入操作之前等待,直到父进程从整个流中读取或关闭流为止。 当调用方和子进程等待彼此完成操作时,会发生死锁条件,并且这两个情况都无法继续。
简单点说就是当数据量较大时,子进程写入的数据占满了重定向流,此时子进程会停止写入,等待父进程从流读取数据,然而父进程会等待直到子进程完成写入流,此时父进程子进程相互等待,程序陷入死锁。
正确的方法应该是在WaitForExit之前调用StandardOutput.ReadToEnd。
原代码父进程在调用StandardOutput.ReadToEnd之前调用WaitforExit,并且子进程写入足够多的文本以填充重定向的流,则会导致死锁情况。 父进程会无限期地等待子进程退出。 子进程会无限期地等待父进程从整个流中读取StandardOutput 。
2. 同时调用StandardError.ReadToEnd 和 StandardOutput.ReadToEnd.
和上面的原因同理,如果父进程调用 StandardOutput.ReadToEnd 后跟 StandardError.ReadToEnd ,并且子进程写入足够的文本来填充其错误流,则会导致死锁情况。 父进程会无限期地等待子进程关闭其 StandardOutput流。 子进程会无限期地等待父进程从整个流中读取 StandardError。
解决方案:
使用异步方式读取执行结果和错误信息,同时加入延时检查。
示例代码为异步读取数据的方法:
using (Process process = new Process())
{
StringBuilder processOutputBuilder = new StringBuilder();
process.StartInfo = new ProcessStartInfo(executablePath, args);
if (workingFolder != null)
{
process.StartInfo.WorkingDirectory = workingFolder;
}
process.StartInfo.CreateNoWindow = true;
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.RedirectStandardError = true;
process.EnableRaisingEvents = true;
process.OutputDataReceived += (sender, eventArgs) =>
{
if (eventArgs.Data != null)
{
processOutputBuilder.AppendLine(eventArgs.Data);
}
else
{
outputWaitHandle.Set();
}
};
process.ErrorDataReceived += (sender, eventArgs) =>
{
if (eventArgs.Data != null)
{
processOutputBuilder.AppendLine(eventArgs.Data);
}
else
{
errorWaitHandle.Set();
}
};
process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();
outputWaitHandle.WaitOne();
errorWaitHandle.WaitOne();
process.CancelOutputRead();
process.CancelErrorRead();
}
参考链接:
ProcessStartInfo hanging on “WaitForExit”? Why?
MSDN: Process.StandardOutput Property
更多推荐
C# 创建Process调用外部程序卡死的原因分析和解决方案
发布评论