Skip to content

4 Finalización

Esperando la finalización de un subproceso

Si queremos que el proceso padre espere a que un subproceso hijo finalice su ejecución, llamaremos desde el proceso padre al método waitFor() del objeto Process correspondiente al subproceso hijo. Como consecuencia el proceso padre permanece bloqueado hasta que el subproceso hijo finaliza su ejecución, retornando el valor de finalización del subproceso (valor retornado por el main o por el método System.exit() desde el hijo).

Si para cuando se ejecuta el método waitFor() en el padre el subproceso hijo ya ha finalizado su ejecución, se retorna inmediatamente, y se puede obtener el valor de finalización del subproceso hijo mediante el método exitValue() del objeto Process correspondiente. Aunque debemos tener cuidado con este método porque si se utiliza sin que el subproceso haya finalizado su ejecución se lanzará la excepción IllegalThreadStateException.

El método waitFor() lanza la excepción InterruptedException si el proceso padre es interrumpido mientras estaba esperando a la finalización del proceso hijo.

Veamos un ejemplo, en el que se lanza un subproceso en el que se rediríge su salida a un fichero y se espera a que dicho subproceso concluya, para posteriormente lanzar un segundo subproceso que usa el fichero generado por el primero. Finalmente se espera a que finalice este segundo subproceso:

void redirectToFileTest() throws IOException, InterruptedException {
    File outFile = new File("out.tmp");
    Process p = new ProcessBuilder("ls", "-la")
        .redirectOutput(outFile)
        .redirectError(ProcessBuilder.Redirect.INHERIT)
        .start();
    int status = p.waitFor();
    if (status == 0) {
        p = new ProcessBuilder("cat" , outFile.toString())
            .inheritIO()
            .start();
        p.waitFor();
    }
}

Finalización de subprocesos

Una de las características de la máquina virtual de Java es que los subprocesos hijos creados por un proceso padre no finalizan automáticamente cuando éste lo hace (terminación en cascada), sino que pueden seguir ejecutándose incluso aunque el padre haya terminado su ejecución.

Sin embargo, el proceso padre puede finalizar la ejecución de un subproceso que haya iniciado previamente, haciendo uso del método destroy() del objeto Process correspondiente.

Dependiendo de la plataforma en la que se esté ejecutando (Windows, Linux, etc.), dicha finalizada podrá ser ser normal o forzada. Para saber si el sistema permite la terminación con normalidad, desde Java 9 podemos usar el método supportsNormalTermination(), que retornará un booleano indicativo de si es posible o no.

Si queremos forzar la terminación del subproceso, podemos llamar al método destroyForcibly().

Ejecución asíncrona de operaciones tras la finalización de un subproceso

Si queremos ejecutar de forma asíncrona alguna operación cuando un subproceso termine su ejecución, podemos usar su método onExit() de la clase Process, que retorna un CompletableFuture<Process>, que será marcado como "completado" cuando el subproceso haya finalizado su ejecución, ya sea normalmente o porque lo hayamos destruido usando cualquiera de los métodos anteriores.

Si, en momento dado, queremos esperar la finalización del subproceso, podemos llamar al método get() del CompetableFuture<Process> retornado por onExit(). Si llamamos a cancel() sobre el completableFuture, no tendrá efecto sobre la ejecución del subproceso.

Veamos un ejemplo:

void startProcessesTest() throws IOException, InterruptedException {
    ProcessBuilder processBuilder = new ProcessBuilder("ls", "-la");
    Process process = processBuilder.start();
    process.onExit().thenAccept(p -> printResults(p));
    // The parent process can go on executing other things.
    // ...
    System.out.println("\nPress enter to continue ...\n");
    System.in.read();
}

private void printResults(Process p) {
    try {
        System.out.printf("Exit %d, status %d%n%s%n%n",
                          p.pid(), p.exitValue(), output(p.getInputStream()));
    } catch (IOException e) {
        System.out.println("Error printing results");
    }
}

private String output(InputStream inputStream) throws IOException {
    try (BufferedReader br = new BufferedReader(new InputStreamReader(inputStream))) {
        return
            br.lines().collect(Collectors.joining(System.getProperty("line.separator")));
    }
}