Получение System.out из подпроцесса как в консоли
Я написал программу, которая запускает и слушает вывод подпроцесса, выводя его в консоль:
public static void main(String[] args) throws IOException {
testProcess("java -Dfile.encoding=UTF-8 -Dconsole.encoding=UTF-8 -cp \"D:\\Users\\ZONT_\\Documents\" Test");
System.out.println("");
testProcess("python \"D:\\Users\\ZONT_\\Documents\\Test.py\"");
}
private static void testProcess(String command) throws IOException {
Process process = Runtime.getRuntime().exec(command);
Scanner scanner = new Scanner(process.getInputStream());
Scanner scanner1 = new Scanner(process.getErrorStream());
while (process.isAlive() || scanner.hasNext() || scanner1.hasNext()) {
if (scanner.hasNext())
System.out.printf("A:%s RL: %s\n", process.isAlive() ? "T" : "F", scanner.nextLine());
if (scanner1.hasNext())
System.err.printf("A:%s RL: %s\n", process.isAlive() ? "T" : "F", scanner1.nextLine());
}
System.out.println("Process dead.");
}
И тестовые программы на Java и Python:
Python
import time
print("Testing standard characters")
print("Waiting...")
for i in range(1, 10):
print(i)
time.sleep(1)
Java
public class Test {
public static void main(String[] args) {
System.out.println("Testing standard characters");
System.out.println("Testing \u044E\u043D\u0438\u043A\u043E\u0434 characters \u3067\u3059");
System.out.println("Waiting...");
for (int i = 0; i < 10; i++) {
System.out.println(i);
try { Thread.sleep(1000); }
catch (InterruptedException e) { e.printStackTrace(); }
}
System.out.println("Done");
}
}
Вывод
Появилась проблема
Вывод в консоль получается корректный, но в Java приложении первая строка выводится сразу, а все остальные только когда подпроцесс завершается. В Python же не выводится ничего, пока подпроцесс не завершится.
Какое поведение программы я ожидаю
Мне нужно, что бы в консоль выводились все сообщения из подпроцессов в тот же момент, когда они и были отправлены (как если бы я их запустил просто из сmd, а не из Java-приложения), при этом очень желательно не изменять код этих подпроцессов, а только в родительской программе, в методе testProcess().
Как мне это сделать? Почему все строки появляются только одновременно, а не по мере их отправки подпроцессом?
UPD
Дебагом понял, что метод hasNext() в строке if (scanner1.hasNext()) блокирует программу до завершения подпроцесса. Как это избежать?
Ответы (1 шт):
Решил проблему следующим изобретением велосипеда
Описание проблемы
- Дело в том, что метод
Scanner.hasNext()блокирует выполнение потока, пока процесс жив и не отправляет данные в свойstdoutилиstderr. Если бы я в цикле слушал только одинInputStream, то проблемы бы не было, но так как в процессе не возникает ошибок, поток зависает в ожидании ничего изstderr, хоть и в другом stream'е есть данные. Следовтельно нужно слушать эти стримы в разных двух потоках, что бы они друг другу не мешали. Для этого я написал классStreamListener, который слушает поток до гибели процесса и отправляет строки в callbackonLineReceived. - В отличии от Java, процесс python по умолчанию не совершает
flushво время вызова функцииprint(). Но это лечится очень просто и без модификации скрипта - нужно при вызове python интерпретатора указывать опцию-u.
Итоговый рабочий код
public static void main(String[] args) throws IOException {
testProcess("java -Dfile.encoding=UTF-8 -Dconsole.encoding=UTF-8 -cp \"D:\\Users\\ZONT_\\Documents\" Test");
System.out.println("");
testProcess("python -u \"D:\\Users\\ZONT_\\Documents\\Test.py\"");
}
private static void testProcess(String command) throws IOException {
Process process = Runtime.getRuntime().exec(command);
StreamListener.create(process, process.getInputStream(),
line -> System.out.printf("A:%s RL: %s\n", process.isAlive() ? "T" : "F", line));
StreamListener.create(process, process.getErrorStream(),
line -> System.err.printf("A:%s RL: %s\n", process.isAlive() ? "T" : "F", line));
while (process.isAlive()) {
try { Thread.sleep(10); }
catch (InterruptedException e) { e.printStackTrace(); }
}
System.out.println("Process dead.");
}
private static class StreamListener extends Thread {
private final Process process;
private final InputStream stream;
private final OnLineReceived listener;
private static StreamListener create(Process process, InputStream stream, OnLineReceived listener) {
return new StreamListener(process, stream, listener);
}
private StreamListener(Process process, InputStream stream, OnLineReceived listener) {
this.process = process;
this.stream = stream;
this.listener = listener;
start();
}
@Override
public void run() {
Scanner scanner = new Scanner(stream);
while (process.isAlive() || scanner.hasNext()) {
if (scanner.hasNext())
listener.onLineReceived(scanner.nextLine());
}
}
}
private interface OnLineReceived {
void onLineReceived(String line);
}
