process.waitFor () никогда не возвращает

95
Process process = Runtime.getRuntime().exec("tasklist");
BufferedReader reader = 
    new BufferedReader(new InputStreamReader(process.getInputStream()));
process.waitFor();
user590444
источник
Обратите внимание, что в JAVA 8 есть перегрузка waitFor, которая позволяет указать время ожидания. Это может быть лучшим выбором, чтобы воздержаться от случая, когда waitFor никогда не возвращается.
Ikaso 01

Ответы:

145

Есть много причин, по которым waitFor()он не возвращается.

Но обычно все сводится к тому, что выполненная команда не завершается.

И для этого опять же может быть много причин.

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

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

Есть хорошая статья, которая объясняет все подводные камни Runtime.exec()и показывает способы их решения под названием «Когда Runtime.exec () не работает» (да, статья датируется 2000 годом, но содержание все еще применимо!)

Иоахим Зауэр
источник
7
Это правильный ответ, но в нем отсутствует образец кода для устранения проблемы. Посмотрите на ответ Питера Лоури полезный код, чтобы узнать, почему waitFor()не возвращается.
ForguesR
83

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

Возможно, у вас есть ошибки, которые вы не читаете. В этом случае приложение остановится и будет ждать вечно. Простой способ обойти это - перенаправить ошибки на обычный вывод.

ProcessBuilder pb = new ProcessBuilder("tasklist");
pb.redirectErrorStream(true);
Process process = pb.start();
BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
String line;
while ((line = reader.readLine()) != null)
    System.out.println("tasklist: " + line);
process.waitFor();
Питер Лоури
источник
4
Для информации: ProcessBuilder - настоящий конструктор, вы можете напрямую написать ProcessBuilder pb = new ProcessBuilder ("tasklist"). RedirectErrorStream (true);
Жан-Франсуа Савар,
3
Я бы лучше использовалpb.redirectError(new File("/dev/null"));
Toochka
@Toochka Просто для информации, redirectErrorдоступно только с Java 1.7
ZhekaKozlov 05
3
Я считаю, что это должен быть принятый ответ, я заменил свой код этим, и он сразу заработал.
Гербен Рампаарт
43

Также из документа Java:

java.lang

Классный процесс

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

Невозможность очистить буфер входного потока (который направляется в выходной поток подпроцесса) из процесса может привести к блокировке подпроцесса.

Попробуй это:

Process process = Runtime.getRuntime().exec("tasklist");
BufferedReader reader =
new BufferedReader(new InputStreamReader(process.getInputStream()));
while ((reader.readLine()) != null) {}
process.waitFor();
RollingBoy
источник
17
Два предостережения: (1) Используйте ProcessBuilder + redirectErrorStream (true), тогда вы в безопасности. В противном случае (2) вам нужен один поток для чтения из Process.getInputStream (), а другой для чтения из Process.getErrorStream (). Просто потратил около четырех часов на выяснение этого (!), Также известного как "The Hard Way".
kevinarpe
1
Вы можете использовать функциональность Apache Commons Exec для одновременного использования потоков stdout и stderr:, DefaultExecutor executor = new DefaultExecutor(); PumpStreamHandler pumpStreamHandler = new PumpStreamHandler(stdoutOS, stderrOS); executor.setStreamHandler(pumpStreamHandler); executor.execute(cmdLine);где stoutOS и stderrOS BufferedOutputStreamя создал для записи в соответствующие файлы.
Matthew Wise
В моем случае я вызывал командный файл из Spring, который внутренне открывает один редактор. Мой код зависал даже после применения кода process.close(). Но когда я открываю поток ввода, как было предложено выше, и немедленно закрываю - проблема исчезает. Итак, в моем случае Spring ждал сигнала закрытия потока. Хотя я использую автоматическое закрытие Java 8.
shaILU
11

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

Согласно сообщению RollingBoy, этот код почти сработал для меня:

Process process = Runtime.getRuntime().exec("tasklist");
BufferedReader reader =
new BufferedReader(new InputStreamReader(process.getInputStream()));
while ((reader.readLine()) != null) {}
process.waitFor();

В моем случае waitFor () не запускался, потому что я выполнял инструкцию без возврата («ip adddr flush eth0»). Легкий способ исправить это - просто убедиться, что вы всегда что-то возвращаете в своем операторе. Для меня это означало выполнение следующего: «ip adddr flush eth0 && echo done». Вы можете читать буфер весь день, но если ничего не возвращается, ваш поток никогда не освободит свое ожидание.

Надеюсь, это кому-то поможет!

Склоны
источник
2
Если у вас нет представителя для комментариев, все равно не пытайтесь обойти это и комментировать . Сделайте это сам по себе и получите от него репутацию!
Иск Фонда Моники
Я не думаю, что это то, process.waitFor()что зависает, это то, reader.readLine()что зависает, если у вас нет вывода. Я попытался использовать waitFor(long,TimeUnit)тайм-аут, если что-то пойдет не так, и обнаружил, что это было чтение зависло. Из-за чего версия с ограниченным сроком действия требует другого потока для чтения ...
osundblad
5

Есть несколько возможностей:

  1. Вы не израсходовали весь вывод процесса stdout .
  2. Вы не израсходовали весь вывод процесса stderr .
  3. Процесс ожидает ввода от вас, а вы его не предоставили или не закрылиstdin .
  4. Процесс крутится в жестком цикле.
Маркиз Лорн
источник
5

Как уже упоминалось, вы должны использовать stderr и stdout .

По сравнению с другими ответами, начиная с Java 1.7, это еще проще. Вам больше не нужно создавать потоки самостоятельно, чтобы читать stderr и stdout .

Просто используйте ProcessBuilderи используйте методы redirectOutputв сочетании с redirectErrorили redirectErrorStream.

String directory = "/working/dir";
File out = new File(...); // File to write stdout to
File err = new File(...); // File to write stderr to
ProcessBuilder builder = new ProcessBuilder();
builder.directory(new File(directory));
builder.command(command);
builder.redirectOutput(out); // Redirect stdout to file
if(out == err) { 
  builder.redirectErrorStream(true); // Combine stderr into stdout
} else { 
  builder.redirectError(err); // Redirect stderr to file
}
Process process = builder.start();
Маркус Венингер
источник
2

По той же причине вы также можете использовать inheritIO()для сопоставления консоли Java с внешней консолью приложения, например:

ProcessBuilder pb = new ProcessBuilder(appPath, arguments);

pb.directory(new File(appFile.getParent()));
pb.inheritIO();

Process process = pb.start();
int success = process.waitFor();
Вивек Диман
источник
2

Вы должны попытаться использовать вывод и ошибку одновременно

    private void runCMD(String CMD) throws IOException, InterruptedException {
    System.out.println("Standard output: " + CMD);
    Process process = Runtime.getRuntime().exec(CMD);

    // Get input streams
    BufferedReader stdInput = new BufferedReader(new InputStreamReader(process.getInputStream()));
    BufferedReader stdError = new BufferedReader(new InputStreamReader(process.getErrorStream()));
    String line = "";
    String newLineCharacter = System.getProperty("line.separator");

    boolean isOutReady = false;
    boolean isErrorReady = false;
    boolean isProcessAlive = false;

    boolean isErrorOut = true;
    boolean isErrorError = true;


    System.out.println("Read command ");
    while (process.isAlive()) {
        //Read the stdOut

        do {
            isOutReady = stdInput.ready();
            //System.out.println("OUT READY " + isOutReady);
            isErrorOut = true;
            isErrorError = true;

            if (isOutReady) {
                line = stdInput.readLine();
                isErrorOut = false;
                System.out.println("=====================================================================================" + line + newLineCharacter);
            }
            isErrorReady = stdError.ready();
            //System.out.println("ERROR READY " + isErrorReady);
            if (isErrorReady) {
                line = stdError.readLine();
                isErrorError = false;
                System.out.println("ERROR::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::" + line + newLineCharacter);

            }
            isProcessAlive = process.isAlive();
            //System.out.println("Process Alive " + isProcessAlive);
            if (!isProcessAlive) {
                System.out.println(":::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: Process DIE " + line + newLineCharacter);
                line = null;
                isErrorError = false;
                process.waitFor(1000, TimeUnit.MILLISECONDS);
            }

        } while (line != null);

        //Nothing else to read, lets pause for a bit before trying again
        System.out.println("PROCESS WAIT FOR");
        process.waitFor(100, TimeUnit.MILLISECONDS);
    }
    System.out.println("Command finished");
}
Эдуардо Рейес
источник
1

Думаю, я наблюдал аналогичную проблему: некоторые процессы запускались, казалось, успешно выполнялись, но не завершались. Функция waitFor () ждала вечно, если я не убил процесс в диспетчере задач.
Однако все работало хорошо в случаях, когда длина командной строки составляла 127 символов или меньше. Если длинные имена файлов неизбежны, вы можете использовать переменные среды, которые могут позволить вам сохранить короткую строку командной строки. Вы можете сгенерировать пакетный файл (используя FileWriter), в котором вы установите свои переменные среды перед вызовом программы, которую вы действительно хотите запустить. Содержимое такой партии может выглядеть так:

    set INPUTFILE="C:\Directory 0\Subdirectory 1\AnyFileName"
    set OUTPUTFILE="C:\Directory 2\Subdirectory 3\AnotherFileName"
    set MYPROG="C:\Directory 4\Subdirectory 5\ExecutableFileName.exe"
    %MYPROG% %INPUTFILE% %OUTPUTFILE%

Последний шаг - запуск этого командного файла с использованием среды выполнения.

Кауз_ат_Ванкувер
источник
1

Вот метод, который мне подходит. ПРИМЕЧАНИЕ. В этом методе есть код, который может не относиться к вам, поэтому попробуйте проигнорировать его. Например, «logStandardOut (...), git-bash и т. Д.».

private String exeShellCommand(String doCommand, String inDir, boolean ignoreErrors) {
logStandardOut("> %s", doCommand);

ProcessBuilder builder = new ProcessBuilder();
StringBuilder stdOut = new StringBuilder();
StringBuilder stdErr = new StringBuilder();

boolean isWindows = System.getProperty("os.name").toLowerCase().startsWith("windows");
if (isWindows) {
  String gitBashPathForWindows = "C:\\Program Files\\Git\\bin\\bash";
  builder.command(gitBashPathForWindows, "-c", doCommand);
} else {
  builder.command("bash", "-c", doCommand);
}

//Do we need to change dirs?
if (inDir != null) {
  builder.directory(new File(inDir));
}

//Execute it
Process process = null;
BufferedReader brStdOut;
BufferedReader brStdErr;
try {
  //Start the command line process
  process = builder.start();

  //This hangs on a large file
  // /programming/5483830/process-waitfor-never-returns
  //exitCode = process.waitFor();

  //This will have both StdIn and StdErr
  brStdOut = new BufferedReader(new InputStreamReader(process.getInputStream()));
  brStdErr = new BufferedReader(new InputStreamReader(process.getErrorStream()));

  //Get the process output
  String line = null;
  String newLineCharacter = System.getProperty("line.separator");

  while (process.isAlive()) {
    //Read the stdOut
    while ((line = brStdOut.readLine()) != null) {
      stdOut.append(line + newLineCharacter);
    }

    //Read the stdErr
    while ((line = brStdErr.readLine()) != null) {
      stdErr.append(line + newLineCharacter);
    }

    //Nothing else to read, lets pause for a bit before trying again
    process.waitFor(100, TimeUnit.MILLISECONDS);
  }

  //Read anything left, after the process exited
  while ((line = brStdOut.readLine()) != null) {
    stdOut.append(line + newLineCharacter);
  }

  //Read anything left, after the process exited
  while ((line = brStdErr.readLine()) != null) {
    stdErr.append(line + newLineCharacter);
  }

  //cleanup
  if (brStdOut != null) {
    brStdOut.close();
  }

  if (brStdErr != null) {
    brStdOut.close();
  }

  //Log non-zero exit values
  if (!ignoreErrors && process.exitValue() != 0) {
    String exMsg = String.format("%s%nprocess.exitValue=%s", stdErr, process.exitValue());
    throw new ExecuteCommandException(exMsg);
  }

} catch (ExecuteCommandException e) {
  throw e;
} catch (Exception e) {
  throw new ExecuteCommandException(stdErr.toString(), e);
} finally {
  //Log the results
  logStandardOut(stdOut.toString());
  logStandardError(stdErr.toString());
}

return stdOut.toString();

}

Саган
источник