2009-03-18 12 views
15

Voglio avviare un processo da Java, leggere l'output e ottenere il codice di ritorno. Ma mentre è in esecuzione, voglio essere in grado di cancellarlo. Comincio dal lancio del processo:Eseguire il programma esterno da Java, leggere l'output, consentire l'interruzione

ProcessBuilder pb = new ProcessBuilder(args); 
pb.redirectErrorStream(true); 
Process proc = pb.start(); 

Se chiamo proc.waitFor(), non posso fare nulla fino a quando le uscite di processo. Quindi suppongo di aver bisogno di qualcosa del genere:

while (true) { 
    see if process has exited 
    capture some output from the process 
    decide if I want to cancel it, and if so, cancel it 
    sleep for a while 
} 

È giusto? Qualcuno può darmi un esempio di come farlo in Java?

risposta

11

Ecco un esempio di quello che penso che si vuole fare:

ProcessBuilder pb = new ProcessBuilder(args); 
pb.redirectErrorStream(true); 
Process proc = pb.start(); 

InputStream is = proc.getInputStream(); 
InputStreamReader isr = new InputStreamReader(is); 
BufferedReader br = new BufferedReader(isr); 

String line; 
int exit = -1; 

while ((line = br.readLine()) != null) { 
    // Outputs your process execution 
    System.out.println(line); 
    try { 
     exit = proc.exitValue(); 
     if (exit == 0) { 
      // Process finished 
     } 
    } catch (IllegalThreadStateException t) { 
     // The process has not yet finished. 
     // Should we stop it? 
     if (processMustStop()) 
      // processMustStop can return true 
      // after time out, for example. 
      proc.destroy(); 
    } 
} 

È possibile migliorarlo :-) Non ho un vero e proprio ambiente per testare ora, ma è possibile trovare qualche informazione in più here.

+2

Cosa succede se non c'è uscita per un po '?Questo non si bloccherà su br.readLine() fino a quando non ci sarà una riga pronta per la lettura? Non sono sicuro di quanto o quanto poco il mio processo invierà. –

+2

Punto di partenza. È possibile creare un thread separato per analizzare se il processo deve fermarsi. Potrebbe essere eseguito parallelamente al ciclo while e distruggere il processo se si blocca per un po 'di tempo (o qualsiasi altra condizione). –

+0

(intendevo "punto buono") –

5

una classe di supporto come questo sarebbe fare il trucco:

public class ProcessWatcher implements Runnable { 

    private Process p; 
    private volatile boolean finished = false; 

    public ProcessWatcher(Process p) { 
     this.p = p; 
     new Thread(this).start(); 
    } 

    public boolean isFinished() { 
     return finished; 
    } 

    public void run() { 
     try { 
      p.waitFor(); 
     } catch (Exception e) {} 
     finished = true; 
    } 

} 

Si potrebbe quindi implementare il ciclo esattamente come lei:

Process p = Runtime.getRuntime().exec("whatever command"); 
ProcessWatcher pw = new ProcessWatcher(p); 
InputStream output = p.getInputStream(); 
while(!pw.isFinished()) { 
    processOutput(output); 
    if(shouldCancel()) p.destroy(); 
    Thread.sleep(500); 
} 

seconda quali condizioni ti fanno venir voglia di distruggere il processo, potresti volerlo fare in un thread separato. Altrimenti, è possibile bloccare mentre si attende l'elaborazione di più programmi da elaborare e non ottenere mai realmente l'opzione per distruggerli.

EDIT: McDowell ha ragione al 100% nel suo commento qui sotto, quindi ho reso volatile la variabile finale.

+1

La variabile "finished" dovrebbe almeno essere dichiarata come "volatile". – McDowell

+0

Questa è la mia preoccupazione; cosa succede se non c'è uscita per un po '? Se chiamo qualcosa come BufferedReader.readLine(), non voglio che si blocchi per sempre fino a quando non c'è uscita; Voglio la possibilità di cancellare. –

+0

È possibile creare un nuovo Runnable con: public void run() { while (! Pw.isFinished()) { if (shouldCancel()) p.destroy(); Thread.sleep (500); } } quindi nuovo thread (myrunnable) .start(). –

0

Cosa ti fa decidere di interrompere il processo: un evento asincrono (come l'input dell'utente) o un evento sincrono (ad esempio, il processo ha eseguito ciò che volevi che facesse)? Sto indovinando che è il primo - l'input dell'utente ti fa decidere di annullare il sottoprocesso.

Inoltre, quanta uscita ci si aspetta dal sottoprocesso? Se è molto, il sottoprocesso potrebbe bloccarsi se non si legge dal suo flusso di output abbastanza rapidamente.

La situazione può variare, ma sembra che probabilmente occorreranno almeno due thread diversi: uno per decidere se annullare il processo e uno che gestisca l'output dal sottoprocesso.

Date un'occhiata qui per un po 'più in dettaglio: http://java.sun.com/developer/JDCTechTips/2005/tt0727.html#2

+0

Sono già in esecuzione in una discussione e qualcuno al di fuori di quella discussione imposterà uno dei miei campi quando è il momento di annullare. –

8

mi consiglia di check-out Apache Commons Exec per evitare di ricreare la ruota. Ha alcune caratteristiche interessanti come la scelta tra l'esecuzione sincrona e quella asincrona, oltre a una soluzione standard per la generazione di un processo di controllo che può aiutare a ritardare l'esecuzione nel caso si blocchi.

+0

Ho combattuto questo perché volevo avere un programma "puro" che usasse solo il core JDK. Ma hai ragione, questo modo è molto meglio e risolve un sacco di problemi che non sapevo stavo firmando per quando ho provato a creare il mio. – GojiraDeMonstah

2

ne dite di questo (vedere come funziona in jcabi-heroku-maven-plugin):

/** 
* Wait for the process to stop, logging its output in parallel. 
* @param process The process to wait for 
* @return Stdout produced by the process 
* @throws InterruptedException If interrupted in between 
*/ 
private String waitFor(final Process process) throws InterruptedException { 
    final BufferedReader reader = new BufferedReader(
     new InputStreamReader(process.getInputStream()) 
    ); 
    final CountDownLatch done = new CountDownLatch(1); 
    final StringBuffer stdout = new StringBuffer(); 
    new Thread(
     new VerboseRunnable(
      new Callable<Void>() { 
       @Override 
       public Void call() throws Exception { 
        while (true) { 
         final String line = reader.readLine(); 
         if (line == null) { 
          break; 
         } 
         System.out.println(">> " + line); 
         stdout.append(line); 
        } 
        done.countDown(); 
        return null; 
       } 
      }, 
      false 
     ) 
    ).start(); 
    try { 
     process.waitFor(); 
    } finally { 
     done.await(); 
     IOUtils.closeQuietly(reader); 
    } 
    return stdout.toString(); 
} 

ps. Ora questa implementazione è disponibile come com.jcabi.log.VerboseProcess classe da jcabi-log artefatto.

Problemi correlati