【问题标题】:StandardOutput.ReadToEnd() hangs [duplicate]StandardOutput.ReadToEnd() 挂起 [重复]
【发布时间】:2011-11-01 21:02:42
【问题描述】:

我有一个程序经常使用外部程序并读取其输出。 使用您通常的流程重定向输出它工作得很好,但是当我尝试阅读它时,一个特定的参数由于某种原因挂起,没有错误消息 - 没有例外,它只是在到达该行时“停止”。 我当然使用一个集中的函数来调用和读取程序的输出,就是这样:

public string ADBShell(string adbInput)
{
    try
    {
        //Create Empty values
        string result = string.Empty;
        string error = string.Empty;
        string output = string.Empty;
        System.Diagnostics.ProcessStartInfo procStartInfo 
            = new System.Diagnostics.ProcessStartInfo(toolPath + "adb.exe");

        procStartInfo.Arguments = adbInput;
        procStartInfo.RedirectStandardOutput = true;
        procStartInfo.RedirectStandardError = true;
        procStartInfo.UseShellExecute = false;
        procStartInfo.CreateNoWindow = true;
        procStartInfo.WorkingDirectory = toolPath;
        System.Diagnostics.Process proc = new System.Diagnostics.Process();
        proc.StartInfo = procStartInfo;
        proc.Start();
        // Get the output into a string
        proc.WaitForExit();
        result = proc.StandardOutput.ReadToEnd();
        error = proc.StandardError.ReadToEnd();  //Some ADB outputs use this
        if (result.Length > 1)
        {
            output += result;
        }
        if (error.Length > 1)
        {
            output += error;
        }
        Return output;
    }
    catch (Exception objException)
    {
        throw objException;
    }
}

挂起的行是result = proc.StandardOutput.ReadToEnd();,但同样,不是每次,只有在发送特定参数(“start-server”)时。所有其他参数都可以正常工作 - 它读取值并返回它。 它的悬挂方式也很奇怪。它不会冻结或给出错误或任何东西,它只是停止处理。就好像它是一个“返回”命令,除了它甚至不返回调用函数,它只是停止一切,接口仍然启动并运行。 以前有人经历过吗?有人知道我应该尝试什么吗?我假设它在流本身中是出乎意料的,但是有没有办法我可以处理/忽略它以便它仍然读取它?

【问题讨论】:

  • 这是我的[相同]问题得到解答的地方:stackoverflow.com/questions/139593/…
  • 您可能对this post 感兴趣,它解释了如何使用 .NET 进程流处理死锁。 MedallionShell 库,简化了进程 io 流的处理
  • 在控制台中以相同的参数运行相同的程序。它在警告后提示用户交互,例如输入密码或确认,这可能是原因。例如,pgAdmin(postgress 数据库管理)挂起,询问不在其配置文件中的数据库的密码。但这只能从控制台运行看到
  • 如果你用过输入流,这个答案适合你:stackoverflow.com/a/29118547/6859121

标签: c# stream freeze redirectstandardoutput


【解决方案1】:

使用BeginOutputReadLine() 提出的解决方案是一个好方法,但在这种情况下,它不适用,因为进程(当然使用WaitForExit())在异步输出完全完成之前退出。

所以,我尝试同步实现它,发现解决方案是使用StreamReader类中的Peek()方法。我添加了对Peek() > -1 的检查,以确保它不是MSDN article 中描述的流的结尾,并且终于可以正常工作并停止挂起!

代码如下:

var process = new Process();
process.StartInfo.CreateNoWindow = true;
process.StartInfo.UseShellExecute = false;
process.StartInfo.RedirectStandardOutput = true;
process.StartInfo.RedirectStandardError = true;
process.StartInfo.WorkingDirectory = @"C:\test\";
process.StartInfo.FileName = "test.exe";
process.StartInfo.Arguments = "your arguments here";

process.Start();
var output = new List<string>();

while (process.StandardOutput.Peek() > -1)
{
    output.Add(process.StandardOutput.ReadLine());
}

while (process.StandardError.Peek() > -1)
{
    output.Add(process.StandardError.ReadLine());
}
process.WaitForExit();

【讨论】:

  • 我刚刚实现了这个更改,我的进程仍然挂在 process.StandardError.ReadLine()...
  • @ganders 可能是你的进程。StandardError.ReadLine() 返回 null?
  • 我把它修好了,但我实现了其他人从另一个问题中回答的东西,这是我使用的答案的链接:stackoverflow.com/questions/139593/…
  • 重新检查后,实际上它“工作”但无法从该过程中获得任何输出。我在这里找到了一个可行的解决方案:stackoverflow.com/questions/139593/…
  • 检查process.StandardOutput.EndOfStream 怎么样?使用你的process.StandardOutput.Peek() &gt; -1,只显示我的多行输出中的第一个
【解决方案2】:

问题是您在StandardOutputStandardError 流上都使用了同步ReadToEnd 方法。这可能会导致您遇到潜在的死锁。这甚至在MSDN 中有描述。那里描述了解决方案。基本上就是:使用异步版本BeginOutputReadLine读取StandardOutput流的数据:

p.BeginOutputReadLine();
string error = p.StandardError.ReadToEnd();
p.WaitForExit();

使用 BeginOutputReadLine 实现异步读取见ProcessStartInfo hanging on "WaitForExit"? Why?

【讨论】:

  • 感谢您的回复。恐怕这不起作用,它一到“StandardError.ReadToEnd();”就仍然挂起。我什至尝试使用“BeginErrorReadLine();”但这也挂了。 DID 唯一起作用的是向“WaitForExit”添加超时。由于这个挂起的特定参数总是几乎立即给出输出,因此我在大约 3 秒时将其超时,一切正常。它不是很优雅,但它确实有效。再次感谢您的帮助。
  • 此解决方案有效,它是 MSDN 文档中描述的解决方案
【解决方案3】:

类似的东西呢:

process.Start();
process.BeginOutputReadLine();
process.BeginErrorReadLine();

process.OutputDataReceived += (sender, args) =>
                               {
                                    var outputData = args.Data;
                                    // ...
                                };
process.ErrorDataReceived += (sender, args) =>
                            {
                                var errorData = args.Data;
                                // ...
                            };
process.WaitForExit();

【讨论】:

    【解决方案4】:

    我遇到了同样的死锁问题。这段代码 sn-p 对我有用。

            ProcessStartInfo startInfo = new ProcessStartInfo("cmd")
            {
                WindowStyle = ProcessWindowStyle.Hidden,
                UseShellExecute = false,
                RedirectStandardInput = true,
                RedirectStandardOutput = true,
                CreateNoWindow = true
            };
    
            Process process = new Process();
            process.StartInfo = startInfo;
            process.Start();
            process.StandardInput.WriteLine("echo hi");
            process.StandardInput.WriteLine("exit");
            var output = process.StandardOutput.ReadToEnd();
            process.Dispose();
    

    【讨论】:

      【解决方案5】:

      优雅且对我有用的是:

      Process nslookup = new Process()
      {
         StartInfo = new ProcessStartInfo("nslookup")
         {
            RedirectStandardInput = true,
            RedirectStandardOutput = true,
            UseShellExecute = false,
            CreateNoWindow = true,
            WindowStyle = ProcessWindowStyle.Hidden
         }
      };
      
      nslookup.Start();
      nslookup.StandardInput.WriteLine("set type=srv");
      nslookup.StandardInput.WriteLine("_ldap._tcp.domain.local"); 
      
      nslookup.StandardInput.Flush();
      nslookup.StandardInput.Close();
      
      string output = nslookup.StandardOutput.ReadToEnd();
      
      nslookup.WaitForExit();
      nslookup.Close();
      

      我找到了这个答案here,诀窍是在标准输入上使用Flush()Close()

      【讨论】:

      • 如果你在使用输入流时卡住了,这个答案是正确的。
      【解决方案6】:

      接受的答案的解决方案对我不起作用。为了避免死锁,我不得不使用任务:

      //Code to start process here
      
      String outputResult = GetStreamOutput(process.StandardOutput);
      String errorResult = GetStreamOutput(process.StandardError);
      
      process.WaitForExit();
      

      GetStreamOutput函数如下:

      private string GetStreamOutput(StreamReader stream)
      {
         //Read output in separate task to avoid deadlocks
         var outputReadTask = Task.Run(() => stream.ReadToEnd());
      
         return outputReadTask.Result;
      }
      

      【讨论】:

      • 我最喜欢这个答案,... MSDN 甚至推荐单独的线程/任务,但它对我来说仍然是死锁。
      • 即使这个变种死锁string cvout = (Task&lt;string&gt;.Run(async () =&gt; { return await p.StandardOutput.ReadToEndAsync(); })).Result;
      • 即使这个变种死锁string cvout = (Task&lt;string&gt;.Run(async () =&gt; { return await p.StandardOutput.ReadToEndAsync().ConfigureAwait(false); })).Result;
      • 其实上面的死锁会持续几分钟,直到一些底层线程超时,然后继续。延迟是不可接受的,但给了我一线希望。
      • 这是我最终得到的结果:stackoverflow.com/a/47213952/4151626
      【解决方案7】:

      我遇到了和错误一样的问题。

      根据您对 Daniel Hilgarth 的回复,我什至没有尝试使用这些代码,尽管我认为它们对我有用。

      因为我仍然希望能够做一些更漂亮的输出,所以最终我决定我会在后台线程中完成这两个输出。

      public static class RunCommands
      {
          #region Outputs Property
      
          private static object _outputsLockObject;
          private static object OutputsLockObject
          { 
              get
              {
                  if (_outputsLockObject == null)
                      Interlocked.CompareExchange(ref _outputsLockObject, new object(), null);
                  return _outputsLockObject;
              }
          }
      
          private static Dictionary<object, CommandOutput> _outputs;
          private static Dictionary<object, CommandOutput> Outputs
          {
              get
              {
                  if (_outputs != null)
                      return _outputs;
      
                  lock (OutputsLockObject)
                  {
                      _outputs = new Dictionary<object, CommandOutput>();
                  }
                  return _outputs;
              }
          }
      
          #endregion
      
          public static string GetCommandOutputSimple(ProcessStartInfo info, bool returnErrorIfPopulated = true)
          {
              // Redirect the output stream of the child process.
              info.UseShellExecute = false;
              info.CreateNoWindow = true;
              info.RedirectStandardOutput = true;
              info.RedirectStandardError = true;
              var process = new Process();
              process.StartInfo = info;
              process.ErrorDataReceived += ErrorDataHandler;
              process.OutputDataReceived += OutputDataHandler;
      
              var output = new CommandOutput();
              Outputs.Add(process, output);
      
              process.Start();
      
              process.BeginErrorReadLine();
              process.BeginOutputReadLine();
      
              // Wait for the process to finish reading from error and output before it is finished
              process.WaitForExit();
      
              Outputs.Remove(process);
      
              if (returnErrorIfPopulated && (!String.IsNullOrWhiteSpace(output.Error)))
              {
                  return output.Error.TrimEnd('\n');
              }
      
              return output.Output.TrimEnd('\n');
          }
      
          private static void ErrorDataHandler(object sendingProcess, DataReceivedEventArgs errLine)
          {
              if (errLine.Data == null)
                  return;
      
              if (!Outputs.ContainsKey(sendingProcess))
                  return;
      
              var commandOutput = Outputs[sendingProcess];
      
              commandOutput.Error = commandOutput.Error + errLine.Data + "\n";
          }
      
          private static void OutputDataHandler(object sendingProcess, DataReceivedEventArgs outputLine)
          {
              if (outputLine.Data == null)
                  return;
      
              if (!Outputs.ContainsKey(sendingProcess))
                  return;
      
              var commandOutput = Outputs[sendingProcess];
      
              commandOutput.Output = commandOutput.Output + outputLine.Data + "\n";
          }
      }
      public class CommandOutput
      {
          public string Error { get; set; }
          public string Output { get; set; }
      
          public CommandOutput()
          {
              Error = "";
              Output = "";
          }
      }
      

      这对我有用,让我不必为读取使用超时。

      【讨论】:

        【解决方案8】:

        以防万一有人在想使用 Windows 窗体和TextBox(或RichTextBox)来显示错误并实时输出进程返回时偶然发现了这个问题(因为它们被写入process.StandardOutput/@ 987654324@).

        您需要使用 OutputDataReceived() / ErrorDataReceived() 才能读取两个流而不会出现死锁,否则(据我所知)没有办法避免死锁,即使是 Fedor 的答案,它现在拥有“答案”标签和最新的最喜欢的,对我没有用。

        但是,当您使用 RichTextBox(或 TextBox)输出数据时,您遇到的另一个问题是如何将数据实时(一旦到达)实际写入文本框。您可以在后台线程OutputDataReceived() / ErrorDataReceived() 中获得对数据的访问权,而您只能从主线程AppendText()

        我首先尝试从后台线程调用process.Start(),然后在主线程为process.WaitForExit() 时在OutputDataReceived() / ErrorDataReceived() 线程中调用BeginInvoke() =&gt; AppendText()

        但是,这导致我的表格冻结并最终永久挂起。经过几天的尝试,我最终得到了下面的解决方案,这似乎工作得很好。

        简而言之,您需要将消息添加到 OutputDataReceived() / ErrorDataReceived() 线程内的并发集合中,而主线程应不断尝试从该集合中提取消息并将它们附加到文本框中:

                    ProcessStartInfo startInfo
                        = new ProcessStartInfo(File, mysqldumpCommand);
        
                    process.StartInfo.FileName = File;
                    process.StartInfo.Arguments = mysqldumpCommand;
                    process.StartInfo.CreateNoWindow = true;
                    process.StartInfo.UseShellExecute = false;
                    process.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
                    process.StartInfo.RedirectStandardInput = false;
                    process.StartInfo.RedirectStandardError = true;
                    process.StartInfo.RedirectStandardOutput = true;
                    process.StartInfo.StandardErrorEncoding = Encoding.UTF8;
                    process.StartInfo.StandardOutputEncoding = Encoding.UTF8;
                    process.EnableRaisingEvents = true;
        
                    ConcurrentQueue<string> messages = new ConcurrentQueue<string>();
        
                    process.ErrorDataReceived += (object se, DataReceivedEventArgs ar) =>
                    {
                        string data = ar.Data;
                        if (!string.IsNullOrWhiteSpace(data))
                            messages.Enqueue(data);
                    };
                    process.OutputDataReceived += (object se, DataReceivedEventArgs ar) =>
                    {
                        string data = ar.Data;
                        if (!string.IsNullOrWhiteSpace(data))
                            messages.Enqueue(data);
                    };
        
                    process.Start();
                    process.BeginErrorReadLine();
                    process.BeginOutputReadLine();
                    while (!process.HasExited)
                    {
                        string data = null;
                        if (messages.TryDequeue(out data))
                            UpdateOutputText(data, tbOutput);
                        Thread.Sleep(5);
                    }
        
                    process.WaitForExit();
        

        这种方法的唯一缺点是,当进程开始在 process.Start()process.BeginErrorReadLine() / process.BeginOutputReadLine() 之间写入消息时,您可能会在极少数情况下丢失消息,请记住这一点。避免这种情况的唯一方法是读取完整的流并(或)仅在进程完成时才能访问它们。

        【讨论】:

          【解决方案9】:

          第一

               // 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();
          

           // 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();
          

          这是来自MSDN

          【讨论】:

            猜你喜欢
            • 2021-04-23
            • 2018-04-23
            • 1970-01-01
            • 1970-01-01
            • 2017-03-14
            • 1970-01-01
            • 2021-11-28
            • 2015-12-07
            • 2020-09-20
            相关资源
            最近更新 更多