【问题标题】:Hanging process when run with .NET Process.Start -- what's wrong?使用 .NET Process.Start 运行时挂起进程——出了什么问题?
【发布时间】:2010-10-01 04:14:15
【问题描述】:

我在 svn.exe 周围编写了一个快速而肮脏的包装器来检索一些内容并对其进行处理,但对于某些输入,它偶尔且可重现地挂起并且无法完成。例如,一个调用是 svn list:

svn list "http://myserver:84/svn/Documents/Instruments/" --xml  --no-auth-cache --username myuser --password mypassword

当我只是从命令外壳执行此命令行时,它运行良好,但它挂在我的应用程序中。我运行它的 c# 代码是:

string cmd = "svn.exe";
string arguments = "list \"http://myserver:84/svn/Documents/Instruments/\" --xml  --no-auth-cache --username myuser --password mypassword";
int ms = 5000;
ProcessStartInfo psi = new ProcessStartInfo(cmd);
psi.Arguments = arguments;
psi.RedirectStandardOutput = true;
psi.WindowStyle = ProcessWindowStyle.Normal;
psi.UseShellExecute = false;
Process proc = Process.Start(psi);
StreamReader output = new StreamReader(proc.StandardOutput.BaseStream, Encoding.UTF8);

proc.WaitForExit(ms);
if (proc.HasExited)
{
    return output.ReadToEnd();
}

这需要完整的 5000 毫秒并且永远不会完成。延长时间没有用。在单独的命令提示符下,它会立即运行,所以我很确定它与等待时间不足无关。然而,对于其他输入,这似乎工作正常。

我也尝试在这里运行单独的 cmd.exe(其中 exe 是 svn.exe,args 是原始 arg 字符串),但仍然出现挂起:

string cmd = "cmd";
string arguments = "/S /C \"" + exe + " " + args + "\"";

我在这里搞砸了什么,我该如何调试这个外部进程的东西?

编辑:

我现在才开始着手解决这个问题。 Mucho 感谢 Jon Skeet 的建议,这确实很有效。不过,我对我的处理方法还有另一个问题,因为我是一个多线程新手。我想就改善任何明显的缺陷或任何其他愚蠢的东西提出建议。我最终创建了一个小类,其中包含标准输出流、一个保存输出的 StringBuilder 和一个告诉它何时完成的标志。然后我使用了 ThreadPool.QueueUserWorkItem 并传入了我的类的一个实例:

ProcessBufferHandler bufferHandler = new ProcessBufferHandler(proc.StandardOutput.BaseStream,
                                                                          Encoding.UTF8);
ThreadPool.QueueUserWorkItem(ProcessStream, bufferHandler);

proc.WaitForExit(ms);
if (proc.HasExited)
{
    bufferHandler.Stop();
    return bufferHandler.ReadToEnd();
}

...和...

private class ProcessBufferHandler
{
    public Stream stream;
    public StringBuilder sb;
    public Encoding encoding;
    public State state;

    public enum State
    {
        Running,
        Stopped
    }

    public ProcessBufferHandler(Stream stream, Encoding encoding)
    {
        this.stream = stream;
        this.sb = new StringBuilder();
        this.encoding = encoding;
        state = State.Running;
    }
    public void ProcessBuffer()
    {
        sb.Append(new StreamReader(stream, encoding).ReadToEnd());
    }

    public string ReadToEnd()
    {
        return sb.ToString();
    }

    public void Stop()
    {
        state = State.Stopped;
    }
}

这似乎可行,但我怀疑这是最好的方法。这合理吗?我可以做些什么来改进它?

【问题讨论】:

    标签: c# processstartinfo


    【解决方案1】:

    一个标准问题:进程可能正在等待您读取其输出。创建一个单独的线程以在您等待它退出时从其标准输出中读取。这有点痛苦,但这很可能是问题所在。

    【讨论】:

    • 是的,就是这样。只有一个 2k 的输出缓冲区,当你不清空它时它会挂起。 BeginOutputReadLine + BeginErrorReadLine 解决了。
    • 你怎么知道它只是一个 2k 的输出缓冲区?在我的应用程序中,我看到这个挂起发生在一个服务器上,但不是另一个......想知道这是否可能是由于输出缓冲区的大小。
    • 您也可以选择根本不读取标准输出(如果您不需要它)。 command.StartInfo.RedirectStandardOutput = false 对我有用。
    • @JonSkeet 我知道了。虽然被调用的进程应该不会从标准输入读取任何内容,但似乎已经这样做了。我通过设置RedirectStandardInput = true 并关闭标准输入流proc.StandardInput.Close(); 来发现。我把这条评论留给那些和我有同样问题的人=)
    • @AviadP.:您可能希望将其添加为单独的答案。请记住,这个答案是在 2009 年首次编写的,早在 async/await 之前 :)
    【解决方案2】:

    Jon Skeet 是正确的!

    如果您不介意在启动 svn 命令后进行轮询,请尝试以下操作:

    Process command = new Process();
    command.EnableRaisingEvents = false;
    command.StartInfo.FileName = "svn.exe";
    command.StartInfo.Arguments = "your svn arguments here";
    command.StartInfo.UseShellExecute = false;
    command.StartInfo.RedirectStandardOutput = true;
    command.Start();
    
    while (!command.StandardOutput.EndOfStream)
    {
        Console.WriteLine(command.StandardOutput.ReadLine());
    }
    

    【讨论】:

    • 我知道这是四年后的事了,但我很难理解上面的代码是如何工作的。 command.Start() 是同步的,对吗?如果是这样的话,上面的代码将如何进入下一行?
    • Process.Start 是同步的,但是一旦执行命令它就会返回,留给用户检查命令何时完成。
    • 以上并没有解决Process.Start挂起的问题...但它确实告诉我们进程何时执行完它的命令然后继续前进。
    • 像魅力一样工作!谢谢!
    • 非常感谢,你救了我。
    【解决方案3】:

    我不得不在客户端机器上放置一个 exe 并使用 Process.Start 来启动它。

    调用应用程序会挂起 - 问题最终出在他们的机器上,假设 exe 是危险的并阻止其他应用程序启动它。

    右键单击exe并转到属性。点击底部安全警告旁边的“解除阻止”。

    【讨论】:

      【解决方案4】:

      我知道这是一篇旧帖子,但也许这会对某人有所帮助。我用它来执行一些使用 .Net TPL 任务的 AWS (Amazon Web Services) CLI 命令。

      我在我的命令执行中做了类似的事情,该任务是在一个 .Net TPL 任务中执行的,该任务是在我的 WinForm 后台工作程序bgwRun_DoWork 方法中创建的,该方法与while(!bgwRun.CancellationPending) 保持一个循环。这包含使用 .Net ThreadPool 类通过新线程从进程读取标准输出。

      private void bgwRun_DoWork(object sender, DoWorkEventArgs e)
      {
        while (!bgwRun.CancellationPending)
        {
         //build TPL Tasks
         var tasks = new List<Task>();
      
         //work to add tasks here
      
         tasks.Add(new Task(()=>{
      
           //build .Net ProcessInfo, Process and start Process here
      
           ThreadPool.QueueUserWorkItem(state =>
             {
                 while (!process.StandardOutput.EndOfStream)
                 {
                     var output = process.StandardOutput.ReadLine();
                     if (!string.IsNullOrEmpty(output))
                     {
                         bgwRun_ProgressChanged(this, new ProgressChangedEventArgs(0, new ExecutionInfo
                         {
                             Type = "ExecutionInfo",
                             Text = output,
                             Configuration = s3SyncConfiguration
                         }));
                     }
      
                     if (cancellationToken.GetValueOrDefault().IsCancellationRequested)
                     {
                           break;
                     }
                 }
             });
         });//work Task
      
         //loop through and start tasks here and handle completed tasks
      
        } //end while
      }
      

      【讨论】:

        【解决方案5】:

        我知道我的 SVN 存储库有时会运行缓慢,所以可能 5 秒还不够长?您是否从断点复制了要传递给进程的字符串,因此您确定它没有提示您输入任何内容?

        【讨论】:

        • 是的,我可以在没有命令行提示的情况下运行确切的命令。这个调用会在不到一秒的时间内从命令行返回,我尝试在我的应用程序中等待长达 30 秒而没有爱,所以它肯定似乎是擅离职守。
        • 您是否尝试过使用 StreamReader 的默认设置,即 StreamReader sr = new StreamReader(proc.StandardOutput);
        • 是的,我一般不能这样做,因为我需要 xml 的特定 UTF-8 编码,但在默认编码有效的特定情况下,没有任何变化。
        【解决方案6】:

        基于Jon Skeet's answer,这就是我在现代 (2021) .NET 5 中的做法

        var process = Process.Start(processStartInfo);
        
        var stdErr = process.StandardError;
        var stdOut = process.StandardOutput;
        
        var resultAwaiter = stdOut.ReadToEndAsync();
        var errResultAwaiter = stdErr.ReadToEndAsync();
        
        await process.WaitForExitAsync();
        
        await Task.WhenAll(resultAwaiter, errResultAwaiter);
        
        var result = resultAwaiter.Result;
        var errResult = errResultAwaiter.Result;
        

        请注意,您不能在错误之前等待标准输出,因为如果标准错误缓冲区先满,等待将挂起(反之亦然)。

        唯一的方法是开始异步读取它们,等待进程退出,然后使用Task.WaitAll完成等待

        【讨论】:

        • 好吧,tbh,将进程退出任务包含在等待中可能会更好......
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2016-01-19
        • 2021-07-15
        • 2011-03-31
        • 1970-01-01
        • 2022-06-10
        相关资源
        最近更新 更多