Подтвердить что ты не робот

Запустить процесс асинхронно и прочитать из 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, поэтому не можем перенаправлять.

4b9b3361

Ответ 1

Поскольку вы указали, что вам нужно прочитать вывод по ходу, нет не многопоточного решения.

Вы можете уменьшить количество потоков до одного за пределами основного потока, хотя:

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

Это, вероятно, не очень хорошая идея, если вы генерируете много результатов из процесса, который вы вызываете, поскольку он может закончиться дисковым пространством... но он, вероятно, будет немного быстрее, поскольку ему не нужно вращаться еще одна тема.

Ответ 2

Посмотрите ExecHelper от OstermillerUtils.

Идея состоит в том, что поток, ожидающий завершения процесса, не просто ждет, но читает ввод от stdout и stderr, если есть доступный вход и регулярно проверяет, завершен ли процесс.

Если вы не выполняете тяжелую обработку с помощью ввода из stdout и stderr, вам может не понадобиться дополнительный поток для обработки ввода. Просто скопируйте ExecHelper и добавьте дополнительные функции/методы для обработки любого нового ввода. Я сделал это раньше, чтобы показать выход процесса во время работы, это не сложно сделать (но я потерял исходный код).

Если вам нужен отдельный поток для обработки ввода, убедитесь, что вы синхронизируете вывод и ошибку StringBuffers, когда эти буферы обновлены или прочитаны.

Еще одна вещь, которую вы, возможно, захотите рассмотреть, - это добавление тайм-аута прерывания. Это немного сложнее реализовать, но для меня было очень ценно: если процесс занимает слишком много времени, процесс уничтожается, что, в свою очередь, не гарантирует, что ничего не останется висящим. Вы можете найти старый (устаревший?) Пример здесь.

Ответ 3

Вам придется идти на компромисс. Вот ваши варианты:

а. Вы можете сделать это с помощью 2 потоков (вместо 3):

Первая тема:

  • читайте с stdout, пока readline не вернет null
  • вызов Process.waitFor()
  • join Тема № 2

Второй поток:

  • читается с stderr, пока readline не вернет null

В. Объедините потоки и используйте Debian annotate-output, чтобы различать 2 потока

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

С. Если это короткоживущий процесс, просто дождитесь окончания его

Д. Если это долгоживущий процесс, вы можете вращаться между читателями с некоторым сном между ними.

Ответ 4

Предполагая, что вы не против потоков ввода и ошибок, которые нужно объединить, вы можете использовать только один поток с помощью:

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