Выполнение консольной команды в Windows и потоковая передача данных в реальном времени чрезвычайно медленны - PullRequest
0 голосов
/ 24 июня 2019

Я потратил несколько дней на поиск способа чтения результатов консольного приложения в режиме реального времени. Каждый способ, которым я сталкивался, делал это почти идентично всем остальным, но как-то не получалось. Всякий раз, когда я запускал код, моя консольная команда не выводила какой-либо текст в течение нескольких минут, что заставило меня предположить, что на самом деле он выводил данные в конце своего выполнения, а не во время. Однако теперь я знаю, что есть какое-то узкое место, и я не верю, что это связано с моим кодом.

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

У меня 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.В нынешнем виде я не вижу пути решения проблемы.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...