2011-08-25 8 views
10

Estoy buscando reemplazar una gran cantidad de mi perl con scala. Una de las cosas que tiendo a hacer mucho es llamar binarios (generalmente C++ compilado, pero podría ser java, otros scripts Perl, scripts q, etc.) proporcionados por otros equipos de mi empresa.Scala - obteniendo una devolución de llamada cuando sale un proceso externo

Por ejemplo, para hacer algunos cálculos matemáticos complejos, comenzaría uno de los binarios externos y luego canalizaría mis entradas. Luego escucharía su stream stdout para obtener resultados, y stderr transmisión para mensajes de diagnóstico. En Perl, haría esto usando un widget POE::Wheel::Run. He encontrado algo análogo (y mucho mejor) en scala, pero me gustaría hacerlo más robusto. Es una pequeña envoltura alrededor de un objeto ProcessIO. Se ve así:

class Exe(command: String, out: String => Unit, err: String => Unit) { 

    import scala.sys.process._ 
    import scala.io._ 
    import java.io._ 
    import scala.concurrent._ 

    val inputStream = new SyncVar[OutputStream]; 

    val process = Process(command).run(
     new ProcessIO(
      stdin => inputStream.put(stdin), 
      stdout => Source.fromInputStream(stdout).getLines.foreach(out), 
      stderr => Source.fromInputStream(stderr).getLines.foreach(err))); 

    def write(s: String): Unit = synchronized { 
     inputStream.get.write((s + "\n").getBytes) 
    } 

    def close(): Unit = { 
     inputStream.get.close 
    } 
} 

me gustaría a continuación, utilizar de esta manera:

val exe = new Exe("tr [a-z] [A-Z]", 
        out => println("o: " + out), 
        err => println("e: " + err)) 
exe.write("lower") 
exe.close() 

que imprime:

o: LOWER 

Esto me obtiene el 90%, pero lo que sería bueno sería obtener una devolución de llamada cuando el proceso finalice. Puede salir porque he cerrado la secuencia de entrada y su ciclo interno se detiene, puede salir solo o puede salir porque lo he matado. En la devolución de llamada, sería bueno saber por qué se detuvo y el código de salida.

Estoy un poco perdido en cuanto a cómo hacer esto, cualquier ayuda sería apreciada (y cualquier edición del código anterior es, por supuesto, bienvenido, soy un poco novato) .

estoy usando 2.9.0.1

+2

Personalmente, creo que apesta que 'Process' no tenga algún tipo de método de votación' isFinished'. Eso es una cosa que cambiaría, aunque la solución provista por didierd parece más como lo que quieres. –

Respuesta

10

Puede esperar al final de un proceso llamando al exitValue. Puede hacer eso en un hilo separado, en el que se realizará la devolución de llamada. Tal clase Process podría ser chulo así:

import scala.concurrent.ops.spawn 
implicit def ProcessWithCallback(p: Process) { 
    def whenTerminatedDo(callback: Int => Unit) = spawn{ 
    val exitValue = p.exitValue; callback(p) 
    } 
} 

A continuación, podría utilizar eso en Exe como desee.

La clase Process dada por la JVM y envuelto por scala.sys.Process es realmente bastante Feable, será difícil no bloquear un hilo

+2

No hay forma de obtener una devolución de llamada sin un hilo separado bloqueado o sondeo (no importa qué soporte se proporcione 'process'). Dicho eso, las encuestas habrían sido agradables. –

+0

@Daniel. Mi problema con el Proceso JVM es que, debido a que no proporciona dicho método (¡ni siquiera una espera temporizada!), No podemos hacer nada mejor que usar un Tema. Si la API de proceso fuera más grande (en java), proporcionando un método como el que se analiza aquí, la JVM podría hacer uso de características específicas de sistema operativo en algunas implementaciones y hacer las cosas por sí mismo si fuera necesario en otras. No estoy familiarizado con la programación del sistema, pero recuerdo SIGCHLD bajo UNIX, no se necesitaba hilo de espera, ni estaba realmente disponible en esos días ;-). ¿Extraño algo? –

+0

SIGCHLD es una interrupción. No hay interrupciones en el modelo de JVM. Bueno, [este enlace] (http://www.ibm.com/developerworks/java/library/i-signalhandling/) sugiere que hay una manera no estándar al respecto. De todos modos, el problema es la falta de un mecanismo de interrupción estándar. –

2

¿Usted ha considerado el desove un nuevo hilo que luego llamar al método de bloqueo process.exitValue()? A continuación, puede llamar a su devolución de llamada.

3

versión actualizada utilizando spawn para crear un nuevo hilo que bloquea y espera a que el código de salida

class Exe(command:String, out:String=>Unit, err:String=>Unit, onExit:Int=>Unit) { 

    import scala.sys.process._ 
    import scala.io._ 
    import java.io._ 
    import scala.concurrent._ 
    import scala.concurrent.ops.spawn 

    val inputStream = new SyncVar[OutputStream]; 

    val process = Process(command).run(
     new ProcessIO(
      stdin => inputStream.put(stdin), 
      stdout => Source.fromInputStream(stdout).getLines.foreach(out), 
      stderr => Source.fromInputStream(stderr).getLines.foreach(err))); 

    spawn { onExit(process.exitValue()) } 

    def write(s:String):Unit = synchronized { 
     inputStream.get.write((s + "\n").getBytes) 
    } 

    def close():Unit = { 
     inputStream.get.close 
    } 
} 

puede utilizar la misma familia

import java.util.concurrent.CountDownLatch 

val latch = new CountDownLatch(1) 

val exe = new Exe("tr [a-z] [A-Z]", 
     out => println("o: " + out), 
     err => println("e: " + err), 
     code=> {println(code) ; latch.countDown() }) 
exe.write("lower") 
exe.close() 

latch.await 

impresiones

o: LOWER 
0 

gracias a todos!

Cuestiones relacionadas