使用 .NET Process.Start 运行时进程挂起 — 出了什么问题?
-
22-07-2019 - |
题
我在 svn.exe 周围编写了一个快速而肮脏的包装器来检索一些内容并对其执行某些操作,但对于某些输入,它偶尔会重复挂起并且无法完成。例如,一个调用是 svn list:
svn list "http://myserver:84/svn/Documents/Instruments/" --xml --no-auth-cache --username myuser --password mypassword
当我从命令 shell 执行此操作时,此命令行运行良好,但它挂在我的应用程序中。我的 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)的建议,这确实非常有效。不过,我对处理这个问题的方法还有另一个问题,因为我是多线程新手。我想要关于改进任何明显的缺陷或其他愚蠢的事情的建议。我最终创建了一个小类,其中包含 stdout 流、一个用于保存输出的 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;
}
}
这似乎有效,但我怀疑这是否是最好的方法。这合理吗?我能做些什么来改善它?
解决方案
一个标准的问题:这个过程可能等着你读它的输出。创建一个单独的线程,而你等待它退出,从它的标准输出读。这是一个有点痛,但是这很可能是问题。
其他提示
乔恩斯基特是正确的金钱!搜索结果 如果你启动后不介意轮询你的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());
}
我知道这是一篇旧文章,但也许这会对某人有所帮助。我用它来执行一些AWS (亚马逊网络服务) 使用 .Net TPL 任务的 CLI 命令。
我在命令执行中做了类似的事情,该命令是在我的 WinForm 后台工作人员中创建的 .Net TPL 任务中执行的 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
}
我知道我的SVN的回购协议可有时缓慢运行,所以也许5秒不够长?你有没有复制要传递到过程,从一个破发点的字符串,所以你是积极的它没有提示你什么吗?