process.waitFor () wird niemals zurückgegeben

95
Process process = Runtime.getRuntime().exec("tasklist");
BufferedReader reader = 
    new BufferedReader(new InputStreamReader(process.getInputStream()));
process.waitFor();
user590444
quelle
Bitte beachten Sie, dass es in JAVA 8 eine waitFor-Überladung gibt, mit der Sie ein Timeout angeben können. Dies ist möglicherweise die bessere Wahl, um auf einen Fall zu verzichten, in dem waitFor niemals zurückkehrt.
Ikaso

Antworten:

145

Es gibt viele Gründe, waitFor()die nicht zurückkehren.

Normalerweise läuft es jedoch darauf hinaus, dass der ausgeführte Befehl nicht beendet wird.

Dies kann wiederum viele Gründe haben.

Ein häufiger Grund ist, dass der Prozess eine Ausgabe erzeugt und Sie nicht aus den entsprechenden Streams lesen. Dies bedeutet, dass der Prozess blockiert wird, sobald der Puffer voll ist, und darauf wartet, dass Ihr Prozess weiter liest. Ihr Prozess wartet wiederum darauf, dass der andere Prozess abgeschlossen ist (was nicht der Fall ist, weil er auf Ihren Prozess wartet, ...). Dies ist eine klassische Deadlock-Situation.

Sie müssen kontinuierlich aus dem Eingabestream des Prozesses lesen, um sicherzustellen, dass er nicht blockiert.

Es gibt einen schönen Artikel, der alle Fallstricke erklärt Runtime.exec()und Wege um sie herum zeigt: "Wenn Runtime.exec () nicht wird" (ja, der Artikel stammt aus dem Jahr 2000, aber der Inhalt gilt immer noch!)

Joachim Sauer
quelle
7
Diese Antwort ist korrekt, es fehlt jedoch ein Codebeispiel zur Behebung des Problems. Schauen Sie sich die Antwort von Peter Lawrey an, um herauszufinden, warum waitFor()sie nicht zurückkehrt.
ForguesR
83

Es scheint, dass Sie die Ausgabe nicht lesen, bevor Sie darauf warten, dass sie beendet wird. Dies ist nur dann in Ordnung, wenn die Ausgabe den Puffer nicht füllt. Wenn dies der Fall ist, wird gewartet, bis Sie die Ausgabe catch-22 gelesen haben.

Vielleicht haben Sie einige Fehler, die Sie nicht lesen. Dies würde dazu führen, dass die Anwendung stoppt und wartet, bis sie für immer wartet. Eine einfache Möglichkeit, dies zu umgehen, besteht darin, die Fehler auf die reguläre Ausgabe umzuleiten.

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
quelle
4
Zur Information: ProcessBuilder ist ein echter Builder. Sie können direkt ProcessBuilder schreiben. Pb = new ProcessBuilder ("tasklist"). RedirectErrorStream (true);
Jean-François Savard
3
Ich würde lieber verwendenpb.redirectError(new File("/dev/null"));
Toochka
@ Toochka Nur zur Information, redirectErrorist nur seit Java 1.7
ZhekaKozlov
3
Sollte die akzeptierte Antwort sein, glaube ich, habe ich meinen Code durch diesen ersetzt und es hat sofort funktioniert.
Gerben Rampaart
43

Auch aus Java doc:

java.lang

Klassenprozess

Da einige native Plattformen nur eine begrenzte Puffergröße für Standardeingabe- und -ausgabestreams bereitstellen, kann das Versäumnis, den Eingabestream sofort zu schreiben oder den Ausgabestream des Unterprozesses zu lesen, dazu führen, dass der Unterprozess blockiert und sogar blockiert wird.

Wenn der Puffer des Eingabestreams (der an den Ausgabestream des Unterprozesses weitergeleitet wird) aus dem Prozess nicht gelöscht wird, kann dies zu einer Blockierung des Unterprozesses führen.

Versuche dies:

Process process = Runtime.getRuntime().exec("tasklist");
BufferedReader reader =
new BufferedReader(new InputStreamReader(process.getInputStream()));
while ((reader.readLine()) != null) {}
process.waitFor();
RollingBoy
quelle
17
Zwei Einschränkungen: (1) Verwenden Sie ProcessBuilder + redirectErrorStream (true), dann sind Sie sicher. Andernfalls (2) benötigen Sie einen Thread zum Lesen aus Process.getInputStream () und einen anderen zum Lesen aus Process.getErrorStream (). Ich habe gerade vier Stunden damit verbracht, dies herauszufinden (!), Auch bekannt als "The Hard Way".
Kevinarpe
1
Sie können die Apache Commons Exec-Funktionalität verwenden, um die Streams stdout und stderr gleichzeitig zu verwenden: DefaultExecutor executor = new DefaultExecutor(); PumpStreamHandler pumpStreamHandler = new PumpStreamHandler(stdoutOS, stderrOS); executor.setStreamHandler(pumpStreamHandler); executor.execute(cmdLine);wobei stoutOS und stderrOS BufferedOutputStreams sind, die ich erstellt habe, um sie in die entsprechenden Dateien zu schreiben.
Matthew Wise
In meinem Fall habe ich eine Batch-Datei von Spring aufgerufen, die intern einen Editor öffnet. Mein Code hing auch nach dem Anwenden des Codes von process.close(). Aber wenn ich den Eingabestream wie oben vorgeschlagen öffne und sofort schließe, verschwindet das Problem. In meinem Fall wartete Spring also auf das Signal zum Schließen des Streams. Obwohl ich Java 8 verwende, kann es automatisch geschlossen werden.
ShaILU
10

Ich möchte den vorherigen Antworten etwas hinzufügen, aber da ich keinen Repräsentanten zum Kommentieren habe, füge ich einfach eine Antwort hinzu. Dies richtet sich an Android-Benutzer, die in Java programmieren.

Laut dem Beitrag von RollingBoy hat dieser Code für mich fast funktioniert:

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

In meinem Fall wurde waitFor () nicht freigegeben, da ich eine Anweisung ohne Rückgabe ausgeführt habe ("ip adddr flush eth0"). Eine einfache Möglichkeit, dies zu beheben, besteht darin, einfach sicherzustellen, dass Sie immer etwas in Ihrer Erklärung zurückgeben. Für mich bedeutete das, Folgendes auszuführen: "ip adddr flush eth0 && echo done". Sie können den Puffer den ganzen Tag lesen, aber wenn nie etwas zurückgegeben wird, gibt Ihr Thread seine Wartezeit niemals frei.

Hoffe das hilft jemandem!

Pisten
quelle
2
Wenn Sie nicht den Repräsentanten zum Kommentieren haben, umgehen Sie es nicht und kommentieren Sie es trotzdem . Machen Sie dies zu einer eigenen Antwort und holen Sie sich einen Repräsentanten davon!
Fund Monica Klage
Ich denke nicht, dass es das ist process.waitFor(), was hängt, es ist das reader.readLine(), was hängt, wenn Sie keine Ausgabe haben. Ich habe versucht, das waitFor(long,TimeUnit)Timeout zu verwenden , wenn etwas schief geht, und festgestellt, dass es das Lesen war, das aufgehängt wurde. Was die Timeout-Version macht, erfordert einen anderen Thread, um das Lesen zu tun ...
Osundblad
5

Es gibt mehrere Möglichkeiten:

  1. Sie haben nicht die gesamte Ausgabe des Prozesses verbraucht stdout.
  2. Sie haben nicht die gesamte Ausgabe des Prozesses verbraucht stderr.
  3. Der Prozess wartet auf Eingaben von Ihnen und Sie haben sie nicht bereitgestellt oder Sie haben die Prozesse nicht geschlossen stdin.
  4. Der Prozess dreht sich in einer harten Schleife.
Marquis von Lorne
quelle
5

Wie andere bereits erwähnt haben, müssen Sie stderr und stdout konsumieren .

Im Vergleich zu den anderen Antworten ist es seit Java 1.7 noch einfacher. Sie müssen keine Threads mehr selbst erstellen, um stderr und stdout zu lesen .

Verwenden Sie einfach die ProcessBuilderund verwenden Sie die Methoden redirectOutputin Kombination mit entweder redirectErroroder 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
quelle
2

Aus dem gleichen Grund können Sie die inheritIO()Java-Konsole auch einer externen App-Konsole zuordnen, z.

ProcessBuilder pb = new ProcessBuilder(appPath, arguments);

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

Process process = pb.start();
int success = process.waitFor();
Vivek Dhiman
quelle
2

Sie sollten versuchen, Ausgabe und Fehler gleichzeitig zu verbrauchen

    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
quelle
1

Ich glaube, ich habe ein ähnliches Problem festgestellt: Einige Prozesse wurden gestartet, schienen erfolgreich zu laufen, wurden jedoch nie abgeschlossen. Die Funktion waitFor () hat ewig gewartet, außer wenn ich den Prozess im Task-Manager abgebrochen habe.
Es funktionierte jedoch alles gut, wenn die Länge der Befehlszeile 127 Zeichen oder weniger betrug. Wenn lange Dateinamen unvermeidlich sind, möchten Sie möglicherweise Umgebungsvariablen verwenden, um die Befehlszeilenzeichenfolge kurz zu halten. Sie können eine Batchdatei (mit FileWriter) generieren, in der Sie Ihre Umgebungsvariablen festlegen, bevor Sie das Programm aufrufen, das Sie tatsächlich ausführen möchten. Der Inhalt einer solchen Charge könnte folgendermaßen aussehen:

    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%

Der letzte Schritt ist das Ausführen dieser Batchdatei mit Runtime.

Kauz_at_Vancouver
quelle
1

Hier ist eine Methode, die für mich funktioniert. HINWEIS: Diese Methode enthält Code, der möglicherweise nicht auf Sie zutrifft. Versuchen Sie daher, ihn zu ignorieren. Zum Beispiel "logStandardOut (...), git-bash usw.".

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
quelle