C# 创建Process调用外部程序卡死的原因分析和解决方案

it2023-06-30  93

最近做项目发现创建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

最新回复(0)