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

Java 异步运行进程并从stdout和stderr读取,java,multithreading,Java,Multithreading,我有一些代码可以运行一个进程,异步读取stdout和stderr,然后在进程完成时进行处理。它看起来像这样: Process process = builder.start(); Thread outThread = new Thread(() -> { try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {

我有一些代码可以运行一个进程,异步读取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();
    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();
    }
我的问题是,我使用3个线程来实现这个异步的“运行并获取输出”任务——我不知道为什么,但我觉得使用3个线程是不对的。我可以从线程池中分配线程,但这仍然会阻塞那些线程

有没有什么我可以做的,也许是使用NIO,将这个线程减少到更少(1?)个?我能想到的任何事情都是不断地旋转一条线(除非我加上几次睡眠),我也不想这么做


注意:我确实需要边读边读(而不是在进程停止时),我确实需要将stdin与stderr分开,这样就无法进行重定向。

因为您已经指定需要边读边读输出,所以没有非多线程解决方案

您可以将线程数减少到主线程以外的线程数,不过:

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
如果您真的不需要在进程结束之前处理错误/输出,您可以稍微简化它,并且只使用如下主线程:

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

其思想是,等待进程完成的线程不只是等待,而是在有输入可用时从stdout和stderr读取输入,并定期检查进程是否已完成

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

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


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

如果您不介意合并输入流和错误流,那么您只能使用一个线程:

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

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

A.您可以使用2个线程(而不是3个线程)完成此操作:

第一个线程:

  • stdout
    读取,直到
    readline
    返回
    null
  • 调用
    Process.waitFor()
  • 连接
    线程#2
  • 第二个线程:

  • stderr
    读取,直到
    readline
    返回null
  • B.合并流并使用Debian的
    注释输出
    来区分这两个流

    如果这是一个短暂的生命过程,就等它结束吧


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

    我认为你不需要最后一个线程。除非您需要在一个循环中运行其中的多个。@ElliottFrisch-您看到的是一个名为
    runAsync(String命令)
    的方法,我需要立即返回该方法(它是异步的)。我如何处理进程的退出代码以及在没有额外线程的情况下完成流的读取?鉴于此,我认为您需要所有三个线程。如果您没有真正从err中读取,并且在您的进程中可能会很快被卡住。这是从两个线程读取。基本上,这里唯一的改进是不需要单独的线程来等待进程退出。在关闭标准输出之前,它不能退出。这不是办法,因为它只适用于非常小的输出。如果输出相对较大(可能超过1kB?),您的主线程肯定会卡住。请阅读底部的注释:)我没有注意到这一点-我认为您没有什么可以做的。为什么一开始使用3个线程是个问题?