CodeGym /Cursos /JAVA 25 SELF /Future, CompletionHandler: gestión de la finalización de ...

Future, CompletionHandler: gestión de la finalización de operaciones

JAVA 25 SELF
Nivel 56, Lección 1
Disponible

1. Future: «Esperaré a que esté listo»

Las operaciones asíncronas en Java son como enviar una carta por correo: no te quedas junto al buzón esperando la respuesta, sino que sigues con tus asuntos. Cuando llegue la carta, simplemente recibes una notificación. En el código, en cambio, hay que entender que la operación ha terminado y obtener el resultado o el error. En Java hay dos enfoques: mediante Future y mediante CompletionHandler.

Con Future obtienes un «resguardo» — una promesa de un resultado futuro. Puedes comprobar de vez en cuando si ya está listo o esperar al resultado llamando a get().

Con CompletionHandler no necesitas esperar: indicas de antemano un controlador y se invocará solo — tanto en caso de éxito como de error.

¿Cómo funciona?

Cuando llamas al método asíncrono read() o write() de AsynchronousFileChannel, puedes obtener un objeto de tipo Future<Integer>. Es como un ticket en una cola electrónica: la operación ha comenzado y puedes preguntar en cualquier momento: «Entonces, ¿ya está listo?».

Firma del método

Future<Integer> read(ByteBuffer dst, long position)

o

Future<Integer> write(ByteBuffer src, long position)
  • dst — búfer al que se leen los datos.
  • src — búfer del que se escriben los datos.
  • position — posición en el archivo para lectura/escritura.

Ejemplo: lectura asíncrona con Future

Código que lee de forma asíncrona los primeros 1024 bytes de un archivo y espera explícitamente el resultado:

import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousFileChannel;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.util.concurrent.Future;
import java.io.IOException;

public class FutureReadExample {
    public static void main(String[] args) {
        Path path = Path.of("example.txt");
        try (AsynchronousFileChannel channel = AsynchronousFileChannel.open(path, StandardOpenOption.READ)) {
            ByteBuffer buffer = ByteBuffer.allocate(1024);

            // Iniciamos la lectura asíncrona
            Future<Integer> future = channel.read(buffer, 0);

            // ... se puede hacer otras tareas mientras se lee el archivo

            // Esperamos a que termine la operación (¡llamada bloqueante!)
            int bytesRead = future.get(); // Puede lanzar InterruptedException, ExecutionException

            System.out.println("Bytes leídos: " + bytesRead);

            // Pasamos a leer desde el búfer
            buffer.flip();
            while (buffer.hasRemaining()) {
                System.out.print((char) buffer.get());
            }
        } catch (Exception e) {
            System.err.println("Error al leer el archivo: " + e);
        }
    }
}
  • El método future.get() bloquea el hilo hasta que la operación termina — es como «esperar junto al teléfono» hasta que llamen.
  • Puedes llamar a future.isDone() para saber si la operación ha finalizado y, solo entonces, llamar a get().
  • Si la operación finaliza con error, get() lanzará ExecutionException (en su interior, la causa).

¿Cuándo es útil Future?

Future es útil cuando quieres lanzar una tarea y después, cuando te convenga, esperar el resultado: leer un archivo, hacer una petición o calcular algo en segundo plano. También es cómodo para lanzar varias operaciones en paralelo y luego recopilar los resultados.

Pero si necesitas verdadera asincronía sin bloqueos y con notificación automática al finalizar, conviene considerar CompletionHandler.

2. CompletionHandler: «Llámame cuando termines»

CompletionHandler es una interfaz que implementas y pasas al método read() o write(). Cuando la operación finalice (o se produzca un error), Java invocará tu controlador. Es como dejar un número de teléfono: «Llámame cuando esté listo».

Firma del método

void read(ByteBuffer dst,
          long position,
          A attachment,
          CompletionHandler<Integer, ? super A> handler)
  • dst — búfer de lectura.
  • position — posición en el archivo.
  • attachment — objeto arbitrario que se pasará al controlador (puede null o, por ejemplo, el nombre del archivo).
  • handler — tu controlador.

Interfaz CompletionHandler

public interface CompletionHandler<V, A> {
    void completed(V result, A attachment);
    void failed(Throwable exc, A attachment);
}
  • completed se llama en caso de éxito; result — número de bytes, attachment — tu objeto.
  • failed se llama en caso de error; exc — la excepción.

Ejemplo: lectura asíncrona con CompletionHandler

import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousFileChannel;
import java.nio.channels.CompletionHandler;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.io.IOException;

public class CompletionHandlerReadExample {
    public static void main(String[] args) throws IOException {
        Path path = Path.of("example.txt");
        AsynchronousFileChannel channel = AsynchronousFileChannel.open(path, StandardOpenOption.READ);

        ByteBuffer buffer = ByteBuffer.allocate(1024);

        channel.read(buffer, 0, "example.txt", new CompletionHandler<Integer, String>() {
            @Override
            public void completed(Integer result, String attachment) {
                System.out.println("Archivo " + attachment + " leído, bytes: " + result);
                buffer.flip();
                while (buffer.hasRemaining()) {
                    System.out.print((char) buffer.get());
                }
                closeChannel();
            }

            @Override
            public void failed(Throwable exc, String attachment) {
                System.err.println("Error al leer el archivo " + attachment + ": " + exc);
                closeChannel();
            }

            private void closeChannel() {
                try {
                    channel.close();
                } catch (IOException e) {
                    System.err.println("Error al cerrar el canal: " + e);
                }
            }
        });

        // Importante: el método main terminará antes de que finalice la lectura si no se "sujeta" el hilo!
        // En las aplicaciones reales el hilo normalmente no termina de inmediato (por ejemplo, servidor, UI).
        try {
            Thread.sleep(500); // Damos tiempo a la operación asíncrona (¡solo para el ejemplo!)
        } catch (InterruptedException ignored) {}
    }
}

Pasamos a read() un controlador anónimo que sabe qué hacer tras finalizar. En completed obtenemos el resultado y lo procesamos, y en failed reaccionamos al error. No olvides cerrar el canal dentro del controlador para no dejar recursos abiertos.

Hay un matiz: el método main puede terminar antes de que acabe la operación asíncrona, por eso en el ejemplo se ha añadido Thread.sleep(500) — solo para poder ver el resultado. En aplicaciones reales este truco no suele ser necesario.

¿Cuándo es más conveniente usar CompletionHandler?

CompletionHandler es adecuado cuando se necesita verdadera asincronía sin esperas ni bloqueos: lanzas la operación y describes qué hacer al finalizar. Es crítico para la UI (JavaFX, Swing) para que la interfaz no «se cuelgue», y útil en servidores — los hilos no quedan esperando, sino que se usan solo cuando hay trabajo.

4. Comparación de Future y CompletionHandler

Enfoque ¿Bloquea el hilo? ¿Cuándo usarlo? Ejemplo de escenario
Future Sí (con get()) Procesamiento secuencial sencillo Copia de archivos, informes
CompletionHandler No Truly async, UI, servidor, operaciones paralelas Servidor, GUI, E/S masiva
  • Future — más fácil de entender, pero requiere bloquear para obtener el resultado.
  • CompletionHandler — un poco más complejo, pero ofrece asincronía real y no bloquea los hilos.

5. Práctica: escritura asíncrona en un archivo con CompletionHandler

Ejemplo que escribe de forma asíncrona una cadena en un archivo e informa del resultado:

import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousFileChannel;
import java.nio.channels.CompletionHandler;
import java.nio.file.Path;
import java.nio.file.StandardOpenOption;
import java.io.IOException;
import java.nio.charset.StandardCharsets;

public class CompletionHandlerWriteExample {
    public static void main(String[] args) throws IOException {
        String text = "¡Hola, mundo asíncrono!";
        ByteBuffer buffer = ByteBuffer.wrap(text.getBytes(StandardCharsets.UTF_8));
        Path path = Path.of("async_output.txt");

        AsynchronousFileChannel channel = AsynchronousFileChannel.open(
                path, StandardOpenOption.WRITE, StandardOpenOption.CREATE);

        channel.write(buffer, 0, path, new CompletionHandler<Integer, Path>() {
            @Override
            public void completed(Integer result, Path attachment) {
                System.out.println("Se han escrito correctamente " + result + " bytes en el archivo " + attachment);
                try {
                    channel.close();
                } catch (IOException e) {
                    System.err.println("Error al cerrar el canal: " + e);
                }
            }

            @Override
            public void failed(Throwable exc, Path attachment) {
                System.err.println("Error al escribir en el archivo " + attachment + ": " + exc);
                try {
                    channel.close();
                } catch (IOException e) {
                    System.err.println("Error al cerrar el canal: " + e);
                }
            }
        });

        // Mantenemos main para ver el resultado (solo para el ejemplo)
        try {
            Thread.sleep(500);
        } catch (InterruptedException ignored) {}
    }
}
1
Tarea
JAVA 25 SELF, nivel 56, lección 1
Bloqueada
Comprobación asíncrona del inicio del archivo con notificación 🕵️‍♀️
Comprobación asíncrona del inicio del archivo con notificación 🕵️‍♀️
1
Tarea
JAVA 25 SELF, nivel 56, lección 1
Bloqueada
Escritura asíncrona de datos telemétricos con metadatos 🛰️
Escritura asíncrona de datos telemétricos con metadatos 🛰️
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION