最近做项目发现创建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调用外部程序卡死的原因分析和解决方案