2009-03-18 24 views
15

Quiero poner en marcha un proceso de Java, lee su salida, y obtener su código de retorno. Pero mientras se está ejecutando, quiero poder cancelarlo. Empiezo con el lanzamiento del proceso:Ejecutar programa externo de Java, lee de salida, permite la interrupción

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

Si llamo proc.waitFor(), no puedo hacer nada hasta que el proceso finalice. Así que estoy asumiendo que necesito algo como esto:

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 
} 

es esto correcto? ¿Alguien puede darme un ejemplo de cómo hacer esto en Java?

Respuesta

11

He aquí un ejemplo de lo que creo que quiere hacer:

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

Puede mejorarla :-) No tengo un entorno real para probarlo ahora, pero se pueden encontrar algunos más información here.

+2

¿Qué pasa si no hay salida por un tiempo?¿No va a colgar esto en br.readLine() hasta que haya una línea lista para leer? No estoy seguro de cuánto o poco enviará mi proceso. –

+2

Ir a punto. Puede crear un hilo separado para analizar si el proceso debe detenerse. Podría correr en paralelo al ciclo while y destruir el proceso si se cuelga por algún tiempo (o cualquier otra condición). –

+0

(i significaba "buen punto") –

5

una clase de ayuda como éste haría el truco:

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; 
    } 

} 

A continuación, aplicar el bucle exactamente como usted la describe:

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

Dependiendo de qué condiciones se hará querer destruir la proceso, es posible que desee hacer eso en un hilo separado. De lo contrario, puede bloquear mientras espera que se procese más salida del programa y nunca tener la opción de destruirlo.

EDITAR: McDowell es 100% correcto en su comentario a continuación, por lo que he hecho la variable terminada volátil.

+1

La variable "finalizada" debe al menos declararse como "volátil". – McDowell

+0

Esa es mi preocupación; ¿Qué pasa si no hay salida por un tiempo? Si llamo a algo como BufferedReader.readLine(), no quiero que cuelgue para siempre hasta que haya salida; Quiero la oportunidad de cancelar. –

+0

Puede crear un Runnable nuevo con: public void run() { while (! Pw.isFinished()) { if (shouldCancel()) p.destroy(); Thread.sleep (500); }} entonces nuevo hilo (myrunnable) .start(). –

0

¿Qué le haría decidir eliminar el proceso: un evento asincrónico (como la entrada del usuario) o un evento sincrónico (por ejemplo, el proceso ha hecho lo que quería que hiciera)? Supongo que es lo primero: la información del usuario te hace decidir cancelar el subproceso.

Además, la cantidad de salida de qué se puede esperar el subproceso para producir? Si es mucho, entonces el subproceso puede bloquearse si no lee de su flujo de salida lo suficientemente rápido.

Su situación puede variar, pero parece que es probable que necesite al menos dos subprocesos diferentes: uno para decidir si se cancela el proceso y otro que maneja la salida del subproceso.

un vistazo aquí para un poco más de detalle: http://java.sun.com/developer/JDCTechTips/2005/tt0727.html#2

+0

ya estoy corriendo en un hilo, y alguien fuera de ese hilo pondré uno de mis campos cuando es el momento para cancelar. –

8

recomiendo echar un vistazo a Apache Commons Exec para evitar la recreación de la rueda. Tiene algunas características interesantes como elegir entre sincrónico vs asíncrono ejecución, así como una solución estándar para el desove de un proceso deorganismo de control que puede ayudar en el tiempo de espera de la ejecución en caso de que se queda atascado.

+0

Combatí esto porque quería tener un programa "puro" que solo utilizara el núcleo JDK. Pero tienes razón, de esta manera es mucho mejor y resuelve muchos problemas que no sabía para lo que me inscribía cuando traté de crear el mío. – GojiraDeMonstah

2

¿Qué tal esto (ver cómo funciona en 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. Ahora esta implementación está disponible como clase com.jcabi.log.VerboseProcess del artefacto jcabi-log.

Cuestiones relacionadas