使用 .NET Process.Start 运行时进程挂起——出了什么问题?
我在 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;
}
}
这似乎有效,但我怀疑这是最好的方法。 这合理吗? 我能做些什么来改善它?
I wrote a quick and dirty wrapper around svn.exe to retrieve some content and do something with it, but for certain inputs it occasionally and reproducibly hangs and won't finish. For example, one call is to svn list:
svn list "http://myserver:84/svn/Documents/Instruments/" --xml --no-auth-cache --username myuser --password mypassword
This command line runs fine when I just do it from a command shell, but it hangs in my app. My c# code to run this is:
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();
}
This takes the full 5000 ms and never finishes. Extending the time doesn't help. In a separate command prompt, it runs instantly, so I'm pretty sure it's unrelated to an insufficient waiting time. For other inputs, however, this seems to work fine.
I also tried running a separate cmd.exe here (where exe is svn.exe and args is the original arg string), but the hang still occurred:
string cmd = "cmd";
string arguments = "/S /C \"" + exe + " " + args + "\"";
What could I be screwing up here, and how can I debug this external process stuff?
EDIT:
I'm just now getting around to addressing this. Mucho thanks to Jon Skeet for his suggestion, which indeed works great. I have another question about my method of handling this, though, since I'm a multi-threaded novice. I'd like suggestions on improving any glaring deficiencies or anything otherwise dumb. I ended up creating a small class that contains the stdout stream, a StringBuilder to hold the output, and a flag to tell when it's finished. Then I used ThreadPool.QueueUserWorkItem and passed in an instance of my class:
ProcessBufferHandler bufferHandler = new ProcessBufferHandler(proc.StandardOutput.BaseStream,
Encoding.UTF8);
ThreadPool.QueueUserWorkItem(ProcessStream, bufferHandler);
proc.WaitForExit(ms);
if (proc.HasExited)
{
bufferHandler.Stop();
return bufferHandler.ReadToEnd();
}
... and ...
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;
}
}
This seems to work, but I'm doubtful that this is the best way. Is this reasonable? And what can I do to improve it?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(6)
一个标准问题:该进程可能正在等待您读取其输出。 创建一个单独的线程,在等待它退出时从其标准输出中读取数据。 虽然有点痛苦,但这很可能就是问题所在。
One standard issue: the process could be waiting for you to read its output. Create a separate thread to read from its standard output while you're waiting for it to exit. It's a bit of a pain, but that may well be the problem.
乔恩·斯基特(Jon Skeet)在金钱方面是正确的!
如果您不介意在启动 svn 命令后进行轮询,请尝试以下操作:
Jon Skeet is right on the money!
If you don't mind polling after you launch your svn command try this:
基于 Jon Skeet 的回答,这就是我在现代(2021).NET 5 中的做法。
请注意,你不能在错误之前等待标准输出,因为如果标准错误缓冲区首先变满,则等待将挂起(与尝试相反的方式相同)。
唯一的方法是开始异步读取它们,等待进程退出,然后使用
Task.WaitAll
完成等待Based on Jon Skeet's answer this is how I do it in modern day (2021) .NET 5
Note that you can't await the standard output before the error, because the wait will hang in case the standard error buffer gets full first (same for trying it the other way around).
The only way is to start reading them asynchronously, wait for the process to exit, and then complete the await by using
Task.WaitAll
我必须在客户端的计算机上放置一个 exe 并使用 Process.Start 来启动它。
调用应用程序将挂起 - 问题最终出在他们的机器上,假设该 exe 是危险的并阻止其他应用程序启动它。
右键单击该 exe 并转到属性。 点击底部安全警告旁边的“解锁”。
I had to drop an exe on a client's machine and use Process.Start to launch it.
The calling application would hang - the issue ended up being their machine assuming the exe was dangerous and preventing other applications from starting it.
Right click the exe and go to properties. Hit "Unblock" toward the bottom next to the security warning.
我知道这是一篇旧文章,但也许这会对某人有所帮助。 我用它来使用 .Net TPL 任务执行一些 AWS (Amazon Web Services) CLI 命令。
我在命令执行中做了类似的事情,该命令在 .Net TPL 任务中执行,该任务是在我的 WinForm 后台工作
bgwRun_DoWork
方法中创建的,该方法使用while(!bgwRun.CancellationPending) 保持循环
。 这包含使用 .Net ThreadPool 类通过新线程从进程读取标准输出。I know this is an old post but maybe this will assist someone. I used this to execute some AWS (Amazon Web Services) CLI commands using .Net TPL tasks.
I did something like this in my command execution which is executed within a .Net TPL Task which is created within my WinForm background worker
bgwRun_DoWork
method which holding a loop withwhile(!bgwRun.CancellationPending)
. This contains the reading of the Standard Output from the Process via a new Thread using the .Net ThreadPool class.我知道我的 SVN 存储库有时会运行缓慢,所以也许 5 秒还不够长? 您是否复制了从断点传递到进程的字符串,因此您确信它不会提示您输入任何内容?
I know my SVN repos can run slow sometimes, so maybe 5 seconds isn't long enough? Have you copied the string you are passing to the process from a break point so you are positive it's not prompting you for anything?