process.waitFor () nigdy nie zwraca

95
Process process = Runtime.getRuntime().exec("tasklist");
BufferedReader reader = 
    new BufferedReader(new InputStreamReader(process.getInputStream()));
process.waitFor();
user590444
źródło
Należy pamiętać, że w JAVA 8 występuje przeciążenie waitFor, które pozwala określić limit czasu. To może być lepszy wybór, aby powstrzymać się od przypadku, gdy funkcja waitFor nigdy nie wraca.
Ikaso

Odpowiedzi:

145

Istnieje wiele powodów, które waitFor()nie wracają.

Ale zwykle sprowadza się to do tego, że wykonywane polecenie nie kończy się.

To znowu może mieć wiele powodów.

Jednym z typowych powodów jest to, że proces generuje pewne dane wyjściowe, a Ty nie czytasz z odpowiednich strumieni. Oznacza to, że proces jest blokowany, gdy tylko bufor się zapełni i czeka, aż proces będzie kontynuował czytanie. Twój proces z kolei czeka na zakończenie drugiego procesu (co nie nastąpi, ponieważ czeka na Twój proces, ...). To jest klasyczna sytuacja impasu.

Musisz stale czytać ze strumienia wejściowego procesów, aby upewnić się, że nie blokuje.

Jest fajny artykuł, który wyjaśnia wszystkie pułapki Runtime.exec()i pokazuje sposoby ich obejścia, nazwany „Kiedy Runtime.exec () nie będzie” (tak, artykuł pochodzi z 2000 roku, ale treść nadal obowiązuje!)

Joachim Sauer
źródło
7
Ta odpowiedź jest poprawna, ale brakuje w niej przykładowego kodu umożliwiającego rozwiązanie problemu. Zapoznaj się z odpowiedzią Petera Lawreya, aby znaleźć przydatny kod, aby dowiedzieć się, dlaczego waitFor()nie zwraca.
ForguesR
83

Wygląda na to, że nie czytasz wyjścia, zanim zaczekasz na jego zakończenie. Jest to dobre tylko wtedy, gdy dane wyjściowe nie wypełniają bufora. Jeśli tak, będzie czekać, aż odczytasz wyjście, catch-22.

Być może masz jakieś błędy, których nie czytasz. Spowodowałoby to zatrzymanie aplikacji i czekanie na wieczne oczekiwanie. Prostym sposobem obejścia tego jest przekierowanie błędów do zwykłego wyjścia.

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();
Peter Lawrey
źródło
4
Dla informacji: ProcessBuilder będąc prawdziwym konstruktorem, możesz bezpośrednio napisać ProcessBuilder pb = new ProcessBuilder ("tasklist"). RedirectErrorStream (true);
Jean-François Savard
3
Wolałbym użyćpb.redirectError(new File("/dev/null"));
Toochka
@Toochka Tylko dla informacji, redirectErrorjest dostępna tylko od wersji Java 1.7
ZhekaKozlov
3
Uważam, że powinna być akceptowana odpowiedź, zastąpiłem swój kod tym i od razu zadziałało.
Gerben Rampaart
43

Również z dokumentu Java:

java.lang

Proces klasowy

Ponieważ niektóre platformy natywne zapewniają ograniczoną wielkość bufora tylko dla standardowych strumieni wejściowych i wyjściowych, niepowodzenie szybkiego zapisania strumienia wejściowego lub odczytu strumienia wyjściowego podprocesu może spowodować zablokowanie, a nawet zakleszczenie podprocesu.

Niepowodzenie wyczyszczenia bufora strumienia wejściowego (który potokuje do strumienia wyjściowego podprocesu) z Process może prowadzić do zablokowania podprocesu.

Spróbuj tego:

Process process = Runtime.getRuntime().exec("tasklist");
BufferedReader reader =
new BufferedReader(new InputStreamReader(process.getInputStream()));
while ((reader.readLine()) != null) {}
process.waitFor();
RollingBoy
źródło
17
Dwa zastrzeżenia: (1) Użyj ProcessBuilder + redirectErrorStream (true), wtedy jesteś bezpieczny. W przeciwnym razie (2) potrzebujesz jednego wątku do odczytu z Process.getInputStream (), a drugiego do odczytu z Process.getErrorStream (). Właśnie spędziłem około czterech godzin, zastanawiając się nad tym (!), Czyli „The Hard Way”.
kevinarpe
1
Możesz użyć funkcji Apache Commons Exec, aby jednocześnie korzystać ze strumieni stdout i stderr: DefaultExecutor executor = new DefaultExecutor(); PumpStreamHandler pumpStreamHandler = new PumpStreamHandler(stdoutOS, stderrOS); executor.setStreamHandler(pumpStreamHandler); executor.execute(cmdLine);gdzie stoutOS i stderrOS to BufferedOutputStreams, które stworzyłem, aby wypisywać do odpowiednich plików.
Matthew Wise
W moim przypadku dzwoniłem do pliku wsadowego Springa, który wewnętrznie otwiera jeden edytor. Mój kod zawiesił się nawet po zastosowaniu kodu process.close(). Ale kiedy otwieram strumień wejściowy zgodnie z powyższą sugestią i natychmiast zamykam - problem znika. Więc w moim przypadku Wiosna czekała na sygnał zamknięcia strumienia. Mimo że używam automatycznego zamykania Java 8.
shaILU
11

Chciałbym coś dodać do poprzednich odpowiedzi, ale ponieważ nie mam przedstawiciela do komentowania, po prostu dodam odpowiedź. Jest to skierowane do użytkowników Androida, którzy programują w języku Java.

Zgodnie z postem od RollingBoy, ten kod prawie działał dla mnie:

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

W moim przypadku funkcja waitFor () nie była zwalniana, ponieważ wykonywałem instrukcję bez powrotu („ip adddr flush eth0”). Łatwym sposobem naprawienia tego jest po prostu upewnienie się, że zawsze zwracasz coś w wyciągu. Dla mnie oznaczało to wykonanie następującego polecenia: "ip adddr flush eth0 && echo done". Możesz czytać bufor przez cały dzień, ale jeśli nic nie zostanie zwrócone, Twój wątek nigdy nie zwolni swojego oczekiwania.

Mam nadzieję, że to komuś pomoże!

Stoki
źródło
2
Jeśli nie masz przedstawiciela do komentowania, nie omijaj tego i nie komentuj . Zrób to samo w sobie i zdobądź z tego reputację!
Załóż pozew Moniki
Nie sądzę, że to się process.waitFor()zawiesza, ale to się reader.readLine()zawiesza, jeśli nie masz wyjścia. Próbowałem użyć waitFor(long,TimeUnit)limitu czasu, jeśli coś pójdzie nie tak i odkryłem, że był to odczyt zawieszony. Co sprawia, że ​​wersja z przekroczonym limitem czasu wymaga innego wątku do wykonania odczytu ...
osundblad
5

Możliwości jest kilka:

  1. Nie zużyłeś wszystkich danych wyjściowych procesu stdout.
  2. Nie zużyłeś wszystkich danych wyjściowych procesu stderr.
  3. Proces oczekuje na dane wejściowe od Ciebie, a Ty ich nie podałeś lub nie zamknąłeś procesu stdin.
  4. Proces wiruje w twardej pętli.
Markiz Lorne
źródło
5

Jak wspominali inni, musisz używać stderr i stdout .

W porównaniu z innymi odpowiedziami, ponieważ Java 1.7 jest jeszcze łatwiejsza. Nie musisz już samodzielnie tworzyć wątków, aby czytać stderr i stdout .

Po prostu użyj ProcessBuilderi użyj tych metod redirectOutputw połączeniu z albo redirectErrorlub 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();
Markus Weninger
źródło
2

Z tego samego powodu możesz również użyć inheritIO()do mapowania konsoli Java z zewnętrzną konsolą aplikacji, na przykład:

ProcessBuilder pb = new ProcessBuilder(appPath, arguments);

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

Process process = pb.start();
int success = process.waitFor();
Vivek Dhiman
źródło
2

Powinieneś spróbować zużyć wyjście i błąd w tym samym czasie

    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");
}
Eduardo Reyes
źródło
1

Myślę, że zauważyłem podobny problem: niektóre procesy rozpoczęły się, wydawały się działać pomyślnie, ale nigdy nie zostały zakończone. Funkcja waitFor () czekała wiecznie, chyba że zabiłem proces w Menedżerze zadań.
Jednak wszystko działało dobrze w przypadkach, gdy długość wiersza poleceń wynosiła 127 znaków lub mniej. Jeśli długie nazwy plików są nieuniknione, możesz chcieć użyć zmiennych środowiskowych, które mogą pozwolić ci zachować krótki ciąg wiersza poleceń. Możesz wygenerować plik wsadowy (za pomocą FileWriter), w którym ustawiasz zmienne środowiskowe przed wywołaniem programu, który chcesz uruchomić. Zawartość takiej partii mogłaby wyglądać następująco:

    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%

Ostatnim krokiem jest uruchomienie tego pliku wsadowego przy użyciu środowiska wykonawczego.

Kauz_at_Vancouver
źródło
1

Oto metoda, która działa dla mnie. UWAGA: W tej metodzie jest kod, który może nie mieć zastosowania, więc spróbuj go zignorować. Na przykład „logStandardOut (...), git-bash itp.”.

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

}

Sagan
źródło