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

时间:2014-08-01 18:08:30

标签: java multithreading

我有一些代码运行一个进程,并从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分开,因此无法进行重定向。

4 个答案:

答案 0 :(得分:3)

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

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

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();
    }

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

答案 1 :(得分:1)

查看OstermillerUtils的ExecHelper

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

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

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

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

答案 2 :(得分:0)

你必须妥协。您可以选择以下选项:

一个。你可以用2个线程(而不是3个)来完成它:

第一个帖子:

  1. stdout读取,直到readline返回null
  2. 致电Process.waitFor()
  3. join线程#2
  4. 第二个帖子:

    1. stderr读取,直到readline返回null
    2. B中。合并流并使用Debian的annotate-output来区分2个流

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

      ℃。如果它是一个短暂的过程,只需等待它的结束

      d。如果它是一个长寿的过程,那么你可以在阅读器之间进行一些睡眠。

答案 3 :(得分:-1)

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

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();