process.waitFor () non ritorna mai


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

Si noti che su JAVA 8 è presente un overload waitFor che consente di specificare un timeout. Questa potrebbe essere una scelta migliore per astenersi da un caso in cui l'attesa per non ritorna mai.
Ikaso

Risposte:


145

Ci sono molte ragioni per cui waitFor()non torna.

Ma di solito si riduce al fatto che il comando eseguito non si chiude.

Anche questo può avere molte ragioni.

Un motivo comune è che il processo produce un output e non si legge dai flussi appropriati. Ciò significa che il processo viene bloccato non appena il buffer è pieno e attende che il processo continui a leggere. Il tuo processo a sua volta attende che l'altro processo finisca (cosa che non farà perché aspetta il tuo processo, ...). Questa è una classica situazione di stallo.

È necessario leggere continuamente dal flusso di input dei processi per assicurarsi che non si blocchi.

C'è un bell'articolo che spiega tutte le insidie Runtime.exec()e mostra i modi per aggirarle chiamato "Quando Runtime.exec () non lo farà" (sì, l'articolo è del 2000, ma il contenuto si applica ancora!)


7
Questa risposta è corretta ma manca un esempio di codice per risolvere il problema. Dai un'occhiata alla risposta di Peter Lawrey per il codice utile per scoprire perché waitFor()non ritorna.
ForguesR

83

Sembra che tu non stia leggendo l'output prima di attendere che finisca. Questo va bene solo se l'output non riempie il buffer. Se lo fa, aspetterà fino a quando non leggerai l'output, catch-22.

Forse hai degli errori che non stai leggendo. In questo modo l'applicazione si fermerà e attenderà per sempre. Un modo semplice per aggirare questo problema è reindirizzare gli errori al normale output.

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
Per info: essendo ProcessBuilder un vero builder, puoi scrivere direttamente ProcessBuilder pb = new ProcessBuilder ("tasklist") redirectErrorStream (true);
Jean-François Savard

3
Preferisco usarepb.redirectError(new File("/dev/null"));
Toochka

@Toochka Solo per informazione, redirectErrorè disponibile solo da Java 1.7
ZhekaKozlov

3
Credo che dovrebbe essere la risposta accettata, ho sostituito il mio codice con questo e ha funzionato immediatamente.
Gerben Rampaart

43

Anche da Java doc:

java.lang

Processo di classe

Poiché alcune piattaforme native forniscono solo una dimensione del buffer limitata per flussi di input e output standard, la mancata scrittura tempestiva del flusso di input o la lettura del flusso di output del sottoprocesso può causare il blocco del sottoprocesso e persino un deadlock.

L'impossibilità di cancellare il buffer del flusso di input (che indirizza al flusso di output del sottoprocesso) da Process può causare il blocco del sottoprocesso.

Prova questo:

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

17
Due avvertenze: (1) Usa ProcessBuilder + redirectErrorStream (true), quindi sei al sicuro. Altrimenti, (2) è necessario un thread per leggere da Process.getInputStream () e un altro per leggere da Process.getErrorStream (). Ho appena trascorso circa quattro ore a capirlo (!), Aka "The Hard Way".
kevinarpe

1
È possibile utilizzare la funzionalità Apache Commons Exec per consumare simultaneamente i flussi stdout e stderr DefaultExecutor executor = new DefaultExecutor(); PumpStreamHandler pumpStreamHandler = new PumpStreamHandler(stdoutOS, stderrOS); executor.setStreamHandler(pumpStreamHandler); executor.execute(cmdLine);:, dove stoutOS e stderrOS sono BufferedOutputStreamstati creati per scrivere su file appropriati.
Matthew Wise

Nel mio caso stavo chiamando un file batch da Spring che apre internamente un editor. Il mio codice era bloccato anche dopo aver applicato il codice di process.close(). Ma quando apro il flusso di input come suggerito sopra e chiudo immediatamente, il problema scompare. Quindi nel mio caso la primavera stava aspettando il segnale di chiusura del flusso. Anche se sto usando Java 8 chiudibile automaticamente.
shaILU

10

Vorrei aggiungere qualcosa alle risposte precedenti, ma poiché non ho il rappresentante per commentare, aggiungerò solo una risposta. Questo è diretto agli utenti Android che programmano in Java.

Secondo il post di RollingBoy, questo codice ha quasi funzionato per me:

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

Nel mio caso, waitFor () non è stato rilasciato perché stavo eseguendo un'istruzione senza ritorno ("ip adddr flush eth0"). Un modo semplice per risolvere questo problema è semplicemente assicurarti di restituire sempre qualcosa nella tua dichiarazione. Per me, ciò significava eseguire quanto segue: "ip adddr flush eth0 && echo done". Puoi leggere il buffer tutto il giorno, ma se non viene restituito nulla, il tuo thread non rilascerà mai la sua attesa.

Spero che aiuti qualcuno!


2
Quando non hai il rappresentante per commentare, non aggirarlo e commentare comunque . Rendi questa una risposta da sola e ottieni rappresentante!
Finanzia la causa di Monica

Non penso che sia il process.waitFor()blocco che si reader.readLine()blocca se non hai output. Ho provato a utilizzare il waitFor(long,TimeUnit)timeout se qualcosa va storto e ho scoperto che era la lettura il blocco. Il che fa sì che la versione con timeout richieda un altro thread per eseguire la lettura ...
osundblad

5

Ci sono diverse possibilità:

  1. Non hai consumato tutto l'output del processo stdout.
  2. Non hai consumato tutto l'output del processo stderr.
  3. Il processo è in attesa di input da parte tua e non lo hai fornito o non hai chiuso il processo stdin.
  4. Il processo sta girando in un ciclo difficile.

5

Come altri hanno già detto, devi consumare stderr e stdout .

Rispetto alle altre risposte, dal momento che Java 1.7 è ancora più facile. Non è più necessario creare thread da soli per leggere stderr e stdout .

Basta usare ProcessBuildere utilizzare i metodi redirectOutputin combinazione con redirectErroro 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

Per lo stesso motivo puoi anche utilizzare inheritIO()per mappare la console Java con la console di app esterne come:

ProcessBuilder pb = new ProcessBuilder(appPath, arguments);

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

Process process = pb.start();
int success = process.waitFor();

2

Dovresti provare a consumare output ed errori nello stesso tempo

    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

Penso di aver notato un problema simile: alcuni processi avviati, sembravano funzionare correttamente ma non sono mai stati completati. La funzione waitFor () stava aspettando per sempre tranne se ho ucciso il processo in Task Manager.
Tuttavia, tutto ha funzionato bene nei casi in cui la lunghezza della riga di comando era di 127 caratteri o inferiore. Se i nomi di file lunghi sono inevitabili, potresti voler utilizzare variabili ambientali, che potrebbero consentire di mantenere breve la stringa della riga di comando. È possibile generare un file batch (utilizzando FileWriter) in cui impostare le variabili ambientali prima di chiamare il programma che si desidera effettivamente eseguire. Il contenuto di un tale lotto potrebbe essere simile a:

    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%

L'ultimo passaggio è l'esecuzione di questo file batch utilizzando Runtime.


1

Ecco un metodo che funziona per me. NOTA: c'è del codice all'interno di questo metodo che potrebbe non essere applicabile a te, quindi prova a ignorarlo. Ad esempio "logStandardOut (...), git-bash, ecc".

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

}


Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.