Process.waitFor(),线程和InputStreams

33

以下是我正在使用的伪代码:

Process proc = runtime.exec(command);
processOutputStreamInThread(proc.getInputStream());
processOutputStreamInThread(proc.getErrorStream());
proc.waitFor()

然而,有时候processOutputStreamInThread看不到任何输出,有时候又可以。大致上来说,这个方法创建了一个命令输出的BufferedInputStream并将其发送到一个日志记录器中。
根据我的观察,我猜测command不需要所有的输出都被倒入由getInputStream()getErrorStream()提供的流中,从而允许流为空。
我的试验结果有以下问题:
(1) java.lang.Process中的waitFor()方法在返回之前是否需要读取执行程序的输出?
文档只说明:

如果必要,导致当前线程等待,直到由此Process对象表示的进程终止。如果子进程已经终止,则此方法立即返回。如果子进程尚未终止,则调用线程将被阻塞,直到子进程退出。

(2) getInputStreamgetErrorStream提供的流需要在什么条件下关闭或者它们是否会自动关闭?
文档只说明:

获取子进程的错误输出流。该流从由此Process对象表示的进程的错误输出流管道获取数据。

实现注意事项:输入流进行缓冲是一个好主意。

一位用户报告说他不得不自己关闭流,但是我至少有一部分时间会出现异常,指示流已经关闭,当我试图这样做时。

编辑:getOutputStream更改为getInputStream,现在已上方呈现。

解决方案:问题最终是,在某些情况下,用于处理输出流的线程直到我的非常短暂的进程完成后才运行,导致输入流没有给我任何数据。 waitFor没有等待执行程序的输出。 相反,程序在收集任何输出之前就运行并终止。

我使用线程,因为我不确定我将获得多少标准错误和标准输出,并且我希望能够同时处理两者,而不会阻塞一个或另一个,只要其中一个可用数据。 但是,由于我的线程不能始终读取执行的程序的输出,因此这是一个非解决方案。

我的最终代码看起来像这样:

ProcessBuilder pb = new ProcessBuilder(cmdargs);
pb.redirectErrorStream(true);
Process proc = pb.start();
processOutputStream(proc.getInputStream());
proc.waitFor()
2个回答

29
如果您的外部进程需要从其stdin读取内容,则必须关闭getOutputStream。否则,您将永远等待waitFor
这里是来自JavaWorld的当Runtime.exec()无法工作时的文章,它描述了exec方法的不同陷阱以及如何避免它们。
根据我的经验,最好消耗子进程的STDOUT和STDERR(直到它们EOF),然后在waitFor中阻塞。希望此时您不必等待太久。
对Kaleb问题的回答。 在正常情况下,您不应该关闭流,但是,由于您正在waitingFor,并且由于某种原因它没有超时,因此如果在输出中遇到某些错误条件并且不想进一步处理子进程的输出,则可能需要关闭这些流。但是,当其STDOUT或STDERR管道在另一端关闭时,子程序是否终止(崩溃)完全取决于该子程序的实现。但是,大多数shell程序将在这种情况下终止。
我真的希望waitFor有一些有意义的超时,并且Process有一种记录的方式可以在决定放弃其监视时清理其资源。

4
关于JavaWorld文章,最终解决方案存在一个bug。如果一个进程非常快地终止,errorGobbler和outputGobbler可能还没有运行并消耗数据。代码应该是:proc.waitFor(); errorGobbler.join(); outputGobbler.join();。这会强制主线程等待流读取输入直到完成。 - Bob Albright
有人能否在这里发布一个代码示例?这样做是否过分要求? - Alexander Mills
waitFor方法等待完成进程的最长时间是多少? - Divyang Shah
1
@DivyangShah。理论上来说,由于无法指定超时时间,它可能会一直等待下去。这确实是“Process”面临的主要问题之一。 - Alexander Pogrebnyak
是的,我们必须有意地在一段时间后告诉。 - Divyang Shah
显示剩余3条评论

2

我认为这可能有点违反直觉,但是:

getOutputStream 获取子进程的输出流。将该流的输出传输到此 Process 对象所代表的进程的标准输入流中。 实现注意事项:最好对输出流进行缓冲。 返回:连接到子进程正常输入的输出流。

我理解为这是主进程的输出流,并附加到子进程的标准输入上,因此当您使用 getOutputStream().write() 写入时实际上是在写入 stdin。

您是否想要使用 .getInputStream()?

返回:连接到子进程正常输出的输入流。

至于 Process.Waitfor(),API 文档说:

如果需要,使当前线程等待,直到此 Process 对象表示的进程终止。 如果子过程已经终止,则此方法立即返回。 如果子过程尚未终止,则调用线程将被阻塞,直到子过程退出。

我会说在调用此方法的线程将被阻塞,直到进程执行完成 - 在此阶段您仍然可以处理输出,具体取决于其他线程,或者它们可能在您的线程返回之前就已经完成了。


我在问题中提到的 getOutputStream() 是一个打字错误。实际上,在我的代码中应该是 getInputStream(),因为处理函数需要一个 InputStream,否则代码无法编译通过。 - Kaleb Pederson
我认为你对我的线程仍在处理输出方面是完全正确的。大多数情况下,当我以调试模式运行时,一切都能成功运行,因为一切都运行得更慢。但是在没有调试器的情况下运行时,通常会失败。我稍后会发布我的解决方法。 - Kaleb Pederson

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接