1. Manejo de errores en operaciones asíncronas
En el código síncrono todo es sencillo: si no se encuentra el archivo o no hay permisos, capturas la excepción al instante en try-catch. En el código asíncrono, especialmente cuando usas callbacks (CompletionHandler), el error puede ocurrir después de que tu método haya terminado — en algún lugar de las entrañas del pool de hilos. Si no se maneja correctamente, el programa puede comportarse de forma impredecible: desde «pérdidas silenciosas» de datos hasta la caída de toda la aplicación.
¿Cómo se propagan los errores a CompletionHandler?
La interfaz CompletionHandler<V, A> tiene dos métodos:
- completed(V result, A attachment) — se invoca si la operación terminó correctamente.
- failed(Throwable exc, A attachment) — se invoca si ocurrió un error.
He aquí un ejemplo de uso:
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousFileChannel;
import java.nio.file.*;
import java.util.concurrent.Future;
import java.io.IOException;
public class AsyncErrorDemo {
public static void main(String[] args) throws Exception {
Path path = Paths.get("nonexistent.txt");
ByteBuffer buffer = ByteBuffer.allocate(1024);
try (AsynchronousFileChannel channel = AsynchronousFileChannel.open(path, StandardOpenOption.READ)) {
channel.read(buffer, 0, buffer, new java.nio.channels.CompletionHandler<Integer, ByteBuffer>() {
@Override
public void completed(Integer result, ByteBuffer attachment) {
System.out.println("Leídos correctamente " + result + " bytes");
}
@Override
public void failed(Throwable exc, ByteBuffer attachment) {
System.out.println("Error al leer el archivo: " + exc.getMessage());
// Se puede registrar en logs, notificar al usuario, propagar el error
}
});
} catch (IOException ex) {
System.out.println("Error al abrir el archivo: " + ex.getMessage());
}
// Damos tiempo a que la operación asíncrona termine (en aplicaciones reales usa CountDownLatch u otros mecanismos)
Thread.sleep(500);
}
}
¿Qué está pasando aquí?
- Si el archivo no existe, se llamará al método failed con la excepción correspondiente (NoSuchFileException).
- Si la operación terminó con éxito, se ejecutará completed.
Ejemplos de errores típicos
- Archivo no encontrado: NoSuchFileException
- Acceso denegado: AccessDeniedException
- Error de lectura/escritura: diferentes subclases de IOException
- Problemas con el búfer: BufferOverflowException, BufferUnderflowException
Registro (logging) e información al usuario
Un error en un callback asíncrono no es motivo de pánico, pero tampoco de hacer como si nada hubiera pasado. Una buena práctica es registrar el error (por ejemplo, mediante Logger) y, si es relevante para el usuario, mostrar un mensaje o invocar un manejador en la UI.
Ejemplo de logging:
@Override
public void failed(Throwable exc, ByteBuffer attachment) {
System.err.println("Error en la operación asíncrona: " + exc);
exc.printStackTrace();
}
En código de producción utiliza registros adecuados (por ejemplo, java.util.logging o Log4j), no System.err.
2. Cancelación de operaciones asíncronas
¿Cuándo puede ser necesaria la cancelación de una operación?
A veces es necesario detener una tarea asíncrona en mitad de su ejecución. Por ejemplo, el usuario se arrepiente y pulsa «Cancelar» durante la descarga de un archivo. O se cierra la ventana de la aplicación y la operación ya no tiene sentido. También puede que al finalizar la aplicación simplemente haya que liberar los recursos de forma ordenada.
Para estos casos, la E/S asíncrona en Java admite la cancelación a través de la interfaz Future. Con ella puedes interrumpir una tarea en ejecución en cualquier momento y no malgastar recursos.
¿Cómo cancelar una operación con Future?
El método read o write de AsynchronousFileChannel devuelve un objeto Future<Integer>. Este objeto dispone del método cancel(boolean mayInterruptIfRunning).
Ejemplo: cancelación de una lectura asíncrona
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousFileChannel;
import java.nio.file.*;
import java.util.concurrent.Future;
import java.io.IOException;
public class AsyncCancelDemo {
public static void main(String[] args) throws Exception {
Path path = Paths.get("bigfile.txt");
ByteBuffer buffer = ByteBuffer.allocate(1024);
try (AsynchronousFileChannel channel = AsynchronousFileChannel.open(path, StandardOpenOption.READ)) {
Future<Integer> future = channel.read(buffer, 0);
// Esperamos un poco y luego cancelamos la operación
Thread.sleep(100);
boolean cancelled = future.cancel(true);
if (cancelled) {
System.out.println("¡La operación de lectura se ha cancelado!");
} else {
System.out.println("No se pudo cancelar la operación (quizá ya haya finalizado)");
}
}
}
}
Aspectos importantes:
- La cancelación solo funciona para operaciones que aún no han terminado.
- Si la operación ya ha finalizado, no se podrá cancelar.
- Tras cancelar, si intentas invocar get() sobre ese Future, se lanzará la excepción CancellationException.
¿Cuándo ya no es posible cancelar la operación?
Si la tarea ya ha terminado — sea con éxito o con error — ya no es posible detenerla.
Además, no todas las implementaciones son capaces de interrumpir operaciones a nivel del sistema operativo. Por ejemplo, con algunos sistemas de archivos la «cancelación» será meramente simbólica: la operación seguirá ejecutándose, pero simplemente ignorarás su resultado.
3. Práctica: manejo de errores y cancelación
Ejemplo 1: manejo del error al leer un archivo inexistente
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousFileChannel;
import java.nio.file.*;
import java.io.IOException;
public class AsyncErrorExample {
public static void main(String[] args) throws Exception {
Path path = Paths.get("no_such_file.txt");
ByteBuffer buffer = ByteBuffer.allocate(1024);
try (AsynchronousFileChannel channel = AsynchronousFileChannel.open(path, StandardOpenOption.READ)) {
channel.read(buffer, 0, buffer, new java.nio.channels.CompletionHandler<Integer, ByteBuffer>() {
@Override
public void completed(Integer result, ByteBuffer attachment) {
System.out.println("La operación finalizó correctamente");
}
@Override
public void failed(Throwable exc, ByteBuffer attachment) {
System.out.println("Error al leer el archivo: " + exc.getClass().getSimpleName() + " - " + exc.getMessage());
}
});
} catch (IOException ex) {
System.out.println("Error al abrir el archivo: " + ex.getMessage());
}
Thread.sleep(500);
}
}
¿Qué veremos en la consola?
Error al abrir el archivo: no_such_file.txt
o, si el error se produce al leer y no al abrir:
Error al leer el archivo: NoSuchFileException - no_such_file.txt
Ejemplo 2: cancelación de una operación larga y finalización correcta
import java.nio.ByteBuffer;
import java.nio.channels.AsynchronousFileChannel;
import java.nio.file.*;
import java.util.concurrent.Future;
import java.io.IOException;
public class AsyncCancelExample {
public static void main(String[] args) throws Exception {
Path path = Paths.get("bigfile.txt");
ByteBuffer buffer = ByteBuffer.allocate(1024 * 1024 * 10); // 10 MB
try (AsynchronousFileChannel channel = AsynchronousFileChannel.open(path, StandardOpenOption.READ)) {
Future<Integer> future = channel.read(buffer, 0);
// Cancelamos la operación después de 50 ms (para experimentar)
Thread.sleep(50);
boolean cancelled = future.cancel(true);
if (cancelled) {
System.out.println("¡La operación de lectura ha sido cancelada!");
} else {
System.out.println("No se pudo cancelar la operación (probablemente ya haya finalizado)");
}
try {
// Intentamos obtener el resultado (lanzará CancellationException)
future.get();
} catch (java.util.concurrent.CancellationException ex) {
System.out.println("Se capturó CancellationException: la operación realmente está cancelada.");
}
}
}
}
4. Buenas prácticas: cómo hacerlo bien
Libera los recursos incluso ante errores
Usa try-with-resources para cerrar los canales automáticamente:
try (AsynchronousFileChannel channel = /* open channel */ null) {
// ...
}
Si utilizas CompletionHandler, no olvides cerrar el canal cuando terminen todas las operaciones. Esto es especialmente importante si realizas varias operaciones asíncronas consecutivas.
No bloquees la UI/el hilo principal
Las operaciones asíncronas están para no bloquear el hilo principal. No llames a future.get() en el hilo de la UI; de lo contrario, se pierde el propósito de la asincronía.
Registra todos los errores
En CompletionHandler implementa siempre el método failed y registra (o propaga) todas las excepciones.
Comprueba la finalización de todas las operaciones antes de cerrar la aplicación
Si la aplicación termina antes de que lo haga la operación, el resultado puede perderse. En demostraciones por consola a veces hay que hacer Thread.sleep(500), pero en aplicaciones reales utiliza CountDownLatch, CompletableFuture u otros mecanismos de sincronización.
No te olvides de la cancelación
Si una operación ya no es necesaria (por ejemplo, el usuario cerró la ventana), cancélala mediante Future.cancel. Ahorrarás recursos y mejorarás la respuesta de la aplicación.
5. Errores habituales al manejar errores y cancelaciones en la E/S asíncrona
Error n.º 1: ignorar el método failed en CompletionHandler.
Si no implementas el manejo de errores, tu aplicación puede comportarse de forma impredecible: los errores «se perderán» y el usuario no sabrá por qué no ocurre nada.
Error n.º 2: no cerrar el canal tras finalizar las operaciones.
Si olvidas cerrar AsynchronousFileChannel, obtendrás fugas de recursos y, posiblemente, bloqueo del archivo en el SO.
Error n.º 3: esperar el resultado de una operación asíncrona en el hilo principal.
Si llamas a future.get() en el hilo de la UI, la interfaz se «congela» y toda la asincronía pierde su sentido.
Error n.º 4: intentar cancelar una operación ya finalizada.
Si llamas a cancel() demasiado tarde — cuando la operación ya terminó — la cancelación no surtirá efecto. No es crítico, pero puede confundir durante la depuración.
Error n.º 5: no comprobar el resultado de la cancelación.
Llamas a cancel(), pero no compruebas el valor devuelto ni manejas CancellationException al invocar get(): la aplicación puede fallar o comportarse de forma extraña.
Error n.º 6: no liberar recursos en caso de error o cancelación.
Si no cierras el canal tras un error o una cancelación, pueden producirse fugas o bloqueos de archivos.
GO TO FULL VERSION