30

我在 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;
    }
}

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

4

6 回答 6

41

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

于 2009-01-13T16:19:09.860 回答
18

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());
}
于 2009-07-15T00:31:27.813 回答
4

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

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

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

在此处输入图像描述

于 2018-08-13T18:42:48.027 回答
1

我知道这是一个旧帖子,但也许这会对某人有所帮助。我使用它来执行一些使用 .Net TPL 任务的AWS (亚马逊网络服务) 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
}
于 2016-04-28T15:05:31.480 回答
0

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

于 2009-01-13T16:21:14.457 回答
0

根据Jon Skeet 的回答,这就是我在现代(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

于 2021-09-08T17:45:47.607 回答