异步运行进程并从stdout和stderr读取

我有一些代码运行一个进程,并从stdout和stderr异步读取,然后在进程完成时进行处理。 它看起来像这样:

Process process = builder.start(); Thread outThread = new Thread(() -> { try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) { // Read stream here } catch (Exception e) { } }); Thread errThread = new Thread(() -> { try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getErrorStream()))) { // Read stream here } catch (Exception e) { } }); outThread.start(); errThread.start(); new Thread(() -> { int exitCode = -1; try { exitCode = process.waitFor(); outThread.join(); errThread.join(); } catch (Exception e) { } // Process completed and read all stdout and stderr here }).start(); 

我的问题在于我使用3个线程来实现这种异步“run-and-get-output”任务 – 我不知道为什么,但我觉得使用3个线程感觉不对。 我可以从线程池中分配线程,但这仍然会阻塞这些线程。

有什么我可以做的,也许是NIO,把它减少到更少的(1?)线程? 我能想到的任何东西都会不断地旋转一个线程(除非我添加一些睡眠),我真的不想这样做……

注意:我确实需要随时阅读(而不是在进程停止时)并且我确实需要将stdin与stderr分开,因此无法进行重定向。

由于您已指定需要随时读取输出,因此没有非multithreading解决方案。

您可以将主线程之外的线程数减少到一个:

 Process process = builder.start(); Thread errThread = new Thread(() -> { try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getErrorStream()))) { // Read stream here } catch (Exception e) { } }); errThread.start(); try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) { // Read stream here } catch (Exception e) { } // we got an end of file, so there can't be any more input. Now we need to wait for stderr/process exit. int exitCode = -1; try { exitCode = process.waitFor(); errThread.join(); } catch (Exception e) { } // Process completed 

如果你真的不需要处理错误/输出,直到进程结束,你可以简化一下,只使用你的主线程,如下所示:

  File stderrFile = File.createTempFile("tmpErr", "out"); File stdoutFile = File.createTempFile("tmpStd", "out"); try { ProcessBuilder builder = new ProcessBuilder("ls /tmp"); Process p = builder.start(); int exitCode = -1; boolean done = false; while (!done) { try { exitCode = p.waitFor(); done = true; } catch (InterruptedException ie) { System.out.println("Interrupted waiting for process to exit."); } } BufferedReader err = new BufferedReader(new FileReader(stderrFile)); BufferedReader in = new BufferedReader(new FileReader(stdoutFile)); .... } finally { stderrFile.delete(); stdoutFile.delete(); } 

如果你从你正在调用的进程中生成大量输出,这可能不是一个好主意,因为它可能会耗尽磁盘空间…但它可能会稍微快一点,因为它不必再旋转另一个线程。

看看OstermillerUtils的ExecHelper。

想法是等待进程完成的线程,不仅仅是等待,而是在有输入可用的情况下从stdout和stderr读取输入,并且会定期检查进程是否已完成。

如果您没有使用stdout和stderr的输入进行任何繁重的处理,则可能不需要额外的线程来处理输入。 只需复制ExecHelper并添加一些额外的函数/方法来处理任何新输入。 我之前已经完成了这个,在进程运行时显示进程输出,这并不困难(但我丢失了源代码)。

如果确实需要一个单独的线程来处理输入,请确保在更新或读取这些缓冲区时同步输出和错误StringBuffers。

您可能想要考虑的另一件事是添加中止超时。 实施起来有点困难但对我来说非常有价值:如果一个过程需要花费太多时间,那么这个过程就会被破坏,从而确保没有任何东西悬而未决。 你可以找到一个古老的(过时的)示例这个要点 。

你必须妥协。 以下是您的选择:

答:你可以用2个线程(而不是3个)来做到这一点:

第一个post:

  1. stdout读取,直到readline返回null
  2. 调用Process.waitFor()
  3. join线程#2

第二个post:

  1. stderr读取,直到readline返回null

B.合并流并使用Debian的annotate-output来区分2个流

http://manpages.debian.org/cgi-bin/man.cgi?query=annotate-output&sektion=1

C.如果这是一个短暂的过程,只需等待它的结束

D.如果这是一个漫长的过程,那么你可以在阅读器之间旋转,两者之间有一些睡眠。

假设您不介意要合并的输入和错误流,您只能使用一个线程:

 builder.redirectErrorStream(true); //merge input and error streams Process process = builder.start(); Thread singleThread = new Thread(() -> { int exitCode = -1; //read from the merged stream try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) { String line; //read until the stream is exhausted, meaning the process has terminated while ((line = reader.readLine()) != null) { System.out.println(line); //use the output here } //get the exit code if required exitCode = process.waitFor(); } catch (Exception e) { } }).start();