Я потратил несколько дней на поиск способа чтения результатов консольного приложения в режиме реального времени. Каждый способ, которым я сталкивался, делал это почти идентично всем остальным, но как-то не получалось. Всякий раз, когда я запускал код, моя консольная команда не выводила какой-либо текст в течение нескольких минут, что заставило меня предположить, что на самом деле он выводил данные в конце своего выполнения, а не во время. Однако теперь я знаю, что есть какое-то узкое место, и я не верю, что это связано с моим кодом.
Трудно передать более минимальный воспроизводимый пример, потому что я считаю, что это усложнит понимание, поэтому, пожалуйста, извините за многословие. Я даже не уверен, что эта проблема вообще может быть воспроизведена, так как это, похоже, проблема с окружающей средой.
У меня Windows 7 (я знаю, я не крутой).
Я использую IntelliJ IDEA IDE (которая почти превосходит неловкость работы Windows 7).
Вот вывод java --version
:
Java 11.0.3 2019-04-16 LTS
Java (TM) SE Runtime Environment 18.9 (сборка 11.0.3 + 12-LTS)
Java HotSpot (TM) 64-битный сервер ВМ 18.9 (сборка 11.0.3 + 12-LTS, смешанный режим)
Чтобы проиллюстрировать проблему, у меня есть следующий код:
public class Main {
private static Timer timer;
public static void main(String[] args) throws IOException {
java.lang.String[] commands = {"cmd", "/c", "sfc", "/scannow"};
ProcessBuilder pBuilder = new ProcessBuilder(commands);
pBuilder.redirectErrorStream();
timer = new Timer();
try {
dbg("Starting process...");
Process p = pBuilder.start();
dbg("Process started.");
InputStream in = p.getInputStream();
final Scanner scanner = new Scanner(in);
new Thread(() -> {
dbg("Thread started.");
int i = 1;
while (scanner.hasNextLine()) {
String nextLine = scanner.nextLine();
dbg("Line " + i + " - " + nextLine);
i++;
}
scanner.close();
dbg("Closing scanner.");
}).start();
dbg("Waiting for exit status result...");
int exitStatus = p.waitFor();
dbg("exit status: " + exitStatus);
p.destroy();
} catch (NullPointerException | InterruptedException | IOException e) {
e.printStackTrace();
}
}
private static void dbg(String messageToAppend) {
System.out.println(timer.getTimePassedAsStr(messageToAppend));
}
}
Я сделал простой класс таймера, чтобы помочь мне отладить код, так как запуск занимает намного больше времени, чем я ожидал. Он работает нормально, поэтому я не буду публиковать код. Тем не менее, вот результат запуска, который я получаю:
[000:00.000] Starting process...
[000:00.149] Process started.
[000:00.150] Input stream created.
[000:00.204] Scanner created.
[000:00.205] Waiting for exit status result...
[000:00.205] Thread started.
[005:51.762] Line 1 -
[005:51.763] Line 2 -
[005:51.763] Line 3 - Beginning system scan. This process will take some time.
[005:51.763] Line 4 -
[005:51.763] Line 5 -
[005:51.764] Line 6 -
[005:51.764] Line 7 - Beginning verification phase of system scan.
[005:51.764] Line 8 -
[012:42.484] Line 9 - Verification 100% complete.
[012:42.485] Line 10 - Windows Resource Protection found corrupt files but was unable to fix some of them.
[012:42.486] Line 11 -
[012:42.486] Line 12 - Details are included in the CBS.Log windir\Logs\CBS\CBS.log. For example
[012:42.487] Line 13 -
[012:42.487] Line 14 - C:\Windows\Logs\CBS\CBS.log
[012:42.487] Line 15 -
[012:42.488] Closing scanner.
[012:42.491] exit status: 0
Обычно, когда я запускаю sfc /scannow
непосредственно из консоли, он запускается почти мгновенно, но когда я запускаю его из моего Java-приложения, почти до 6-минутной задержки перед тем, как я получаю какой-либо вывод.
Я также пытался выполнить его через интерпретатор CMD, например:
java.lang.String[] commands = {"cmd", "/c", "sfc", "/scannow"};
Но я получаю тот же результат. Я также попытался запустить его как один поток, например:
public static void runSfc() throws IOException {
timer = new Timer();
Runtime rt = Runtime.getRuntime();
String[] commands = {"sfc", "/scannow"};
dbg("Running command...");
Process proc = rt.exec(commands);
BufferedReader stdInput = new BufferedReader(new InputStreamReader(proc.getInputStream()));
BufferedReader stdError = new BufferedReader(new InputStreamReader(proc.getErrorStream()));
String s;
int i = 1;
dbg("Here is the standard output of the command:");
while ((s = stdInput.readLine()) != null) {
dbg("Line " + i + " - " + s);
i++;
}
i = 1;
dbg("Here is the standard error of the command (if any):");
while ((s = stdError.readLine()) != null) {
dbg("Line " + i + " - " + s);
i++;
}
}
Похоже, что каждый последующий прогон, будь то однопоточный или многопоточный, занимает примерно одинаковое количество времени или даже больше, а затем по непонятным причинам снова становится короче. Последний запуск стоит за 15 минут до того, как я увидел первую строку вывода в окне консоли.
Приложение должно быть запущено от имени администратора, если это имеет какое-то значение?
Не имеет значения, запускаю ли я код из своей среды IDE или компилирую его и запускаю из командной строки.
Я попытался переустановить Java - без разницы.
Я пытался использовать версию? вместо этого - без разницы.
Любопытно, что когда сканирование не может быть запущено из-за того, что оно уже выполняется в другом месте, оно начало свой первоначальный вывод примерно через 25 секунд (снижение более чем на 5 минут). То же самое происходит и при запуске в потоке.
[000:00.000] Running command...
[000:00.203] Here is the standard output of the command:
[000:25.508] Line 1 -
[000:25.509] Line 2 -
[000:25.509] Line 3 - Beginning system scan. This process will take some time.
[000:25.509] Line 4 -
[000:25.509] Line 5 -
[000:25.509] Line 6 - Another servicing or repair operation is currently running.
[000:25.509] Line 7 -
[000:25.509] Line 8 - Wait for this to finish and run sfc again.
[000:25.509] Line 9 -
[000:25.510] Here is the standard error of the command (if any):
[000:25.510] End of execution
Вопрос 1. Почему процессу требуется что-то более 5 минут для вывода чего-либо, при запуске его из командной строки выводится практически мгновенно? Кто-нибудь еще на машине с Windows испытывает это?
Вопрос 2: Я хотел получать обновления прогресса по мере сканирования. Я не осознавал, что, поскольку прогрессу обновления предшествует символ \ r и он не заканчивается символом \ n, пока процесс не достигнет 100%, я не увижу ни одно из сообщений об обновлении, пока сканирование не завершится, поскольку он никогда не регистрируется как получивший строку вывода. Очевидно, мне нужно читать байты вместо строк. Кто-нибудь знает способ чтения такого типа текста (который начинается с \ r и не заканчивается на \ n до конца процесса) без необходимости заново изобретать колесо?
EDIT:
Среднее среднее время выполнения при непосредственном выполнении через CLI
13 м 53 с (5 пробежек)
Среднее среднее время выполнения при выполнении через приложение Java
12 м 32 с (5 пробежек)
Таким образом, похоже, что время выполнения в Java-приложении примерно такое же, как при его непосредственном выполнении в консоли, и что время выполнения совсем не медленное.Просто требуется много времени, прежде чем отобразится первая строка вывода.
Я могу только заключить, что это действительно должна быть проблема очистки буфера, как предложила @Andreas.В нынешнем виде я не вижу пути решения проблемы.