process.waitFor () ніколи не повертається


95
Process process = Runtime.getRuntime().exec("tasklist");
BufferedReader reader = 
    new BufferedReader(new InputStreamReader(process.getInputStream()));
process.waitFor();

Зверніть увагу, що на JAVA 8 існує функція очікування перевантаження, що дозволяє вказати час очікування. Це може бути кращим вибором утриматися від випадку, коли waitFor ніколи не повертається.
Ікасо

Відповіді:


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 ("список завдань"). RedirectErrorStream (true);
Жан-Франсуа Савард

3
Краще скористаюсяpb.redirectError(new File("/dev/null"));
Toochka

@Toochka Лише для інформації, redirectErrorдоступний лише з Java 1.7
ZhekaKozlov

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

17
Два застереження: (1) Використовуйте ProcessBuilder + redirectErrorStream (true), тоді ви в безпеці. В іншому випадку (2) вам потрібен один потік для читання з Process.getInputStream (), а інший - для читання з Process.getErrorStream (). Щойно витратив близько чотирьох годин, вигадуючи це (!), Він же "Важкий шлях".
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(). Але коли я відкриваю вхідний потік, як було запропоновано вище, і негайно закриваю - проблема зникає. Тож у моєму випадку Весна чекав сигналу закриття потоку. Незважаючи на те, що я використовую Java 8, що закривається автоматично.
shaILU

10

Я хотів би додати щось до попередніх відповідей, але оскільки у мене немає представника для коментарів, я просто додам відповідь. Це спрямовано на користувачів 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%

Останнім кроком є ​​запуск цього командного файлу за допомогою Runtime.


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

}


Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.