2011-08-25 10 views
10

Zamierzam zastąpić wiele moich perl scala. Jedną z rzeczy, które często robię, to binarne wywołania (zwykle kompilowane w C++, ale może to być Java, inne skrypty perla, q skrypty itp.) Dostarczone mi przez inne zespoły w mojej firmie.Scala - uzyskiwanie wywołania zwrotnego, gdy zewnętrzny proces kończy się.

Na przykład, aby wykonać skomplikowaną matematykę, zacznę od jednego z zagranicznych plików binarnych, a następnie dodam do niego dane wejściowe. Następnie wysłucham jego strumienia stdout dla wyników i strumienia stderr dla komunikatów diagnostycznych. W perlu zrobiłbym to za pomocą widgetu POE::Wheel::Run. Wymyśliłem coś podobnego (i dużo ładniejszego) w scala, ale chciałbym, aby było bardziej solidne. Jest to małe opakowanie wokół obiektu ProcessIO. Wygląda to tak:

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

bym następnie używać go tak:

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

Który wypisuje:

o: LOWER 

To dostaje mi 90% tam, ale co byłoby miło byłoby uzyskać wywołanie zwrotne po zakończeniu procesu. Może wyjść, ponieważ zamknąłem strumień wejściowy i jego wewnętrzna pętla zatrzymuje się, może wyjść samodzielnie lub może wyjść, ponieważ ją zabiłem. W wywołaniu zwrotnym dobrze byłoby wiedzieć, dlaczego się zatrzymał, i kod wyjścia.

Jestem trochę zagubiony, jak to zrobić, każda pomoc byłaby doceniona (i wszelkie zmiany powyższego kodu są oczywiście mile widziane - jestem trochę noob) .

Używam 2.9.0.1

+2

Osobiście uważam, że to jest do bani, że 'Proces' nie ma jakiejś metody odpytywania' isFinished'. To jedna rzecz, którą chciałbym zmienić, chociaż rozwiązanie dostarczone przez didierd wydaje się bardziej podobne do tego, co chcesz. –

Odpowiedz

10

Możesz poczekać na koniec procesu wywołującego exitValue. Możesz to zrobić w osobnym wątku, w którym nastąpi wywołanie zwrotne. Może klasa Process może być pimped tak:

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

Można wtedy użyć tego w Exe jak chcesz.

Klasa Process podane przez JVM i owinięte scala.sys.Process jest naprawdę dość feable, trudno będzie, aby nie blokować wątku

+2

Nie można uzyskać wywołania zwrotnego bez zablokowania oddzielnego wątku lub odpytywania (bez względu na to, jaki "proces obsługi" obsługiwał). Powiedział, że głosowanie byłoby miłe. –

+0

@ Daniel. Mój problem z procesem JVM polega na tym, że ponieważ nie zapewnia on takiej metody (nawet oczekiwania na czas!), Nie możemy zrobić nic lepszego niż użycie wątku. Jeśli proces API był większy (w java), dostarczając metodę taką, jak tutaj omawiana, JVM może korzystać z funkcji specyficznych dla systemu operacyjnego w niektórych implementacjach i robić same wątki w razie potrzeby w innych. Nie jestem zaznajomiony z programowaniem systemowym, ale pamiętam SIGCHLD pod UNIX, nie było potrzeby czekania na wątek - ani naprawdę dostępny w tamtych czasach ;-). Czy coś mi brakuje? –

+0

SIGCHLD jest przerwą. W modelu JVM nie ma przerw. Cóż, [ten link] (http://www.ibm.com/developerworks/java/library/i-signalhandling/) sugeruje, że jest to niestandardowy sposób. W każdym razie problemem jest brak standardowego mechanizmu przerywania. –

2

Czy uważane tarła nowego wątku, który będzie następnie wywołać metodę blokowania process.exitValue()? Następnie możesz zadzwonić do swojego oddzwonienia.

3

zaktualizowana wersja użyciu spawn tworzyć nowy wątek, który blokuje i czeka na kod wyjścia

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

można używać jak tego

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 

wydruków

o: LOWER 
0 

dziękuję wszystkim!

Powiązane problemy