1. Gestion des erreurs dans les opérations asynchrones
Dans le code synchrone, c’est simple: si le fichier est introuvable ou l’accès refusé, vous interceptez immédiatement l’exception dans try-catch. Dans le code asynchrone, surtout lorsque vous utilisez des callbacks (CompletionHandler), une erreur peut survenir après la fin de votre méthode — quelque part au fond du pool de threads. Si elle n’est pas gérée correctement, l’application peut se comporter de manière imprévisible: d’une perte de données « silencieuse » à la chute de toute l’application.
Comment les erreurs sont-elles transmises à CompletionHandler ?
L’interface CompletionHandler<V, A> expose deux méthodes :
- completed(V result, A attachment) — est appelée si l’opération a réussi.
- failed(Throwable exc, A attachment) — est appelée s’il s’est produit une erreur.
Voici un exemple d’utilisation :
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("Lecture réussie de " + result + " octets");
}
@Override
public void failed(Throwable exc, ByteBuffer attachment) {
System.out.println("Erreur de lecture du fichier: " + exc.getMessage());
// Vous pouvez journaliser, avertir l’utilisateur, propager l’erreur plus loin
}
});
} catch (IOException ex) {
System.out.println("Erreur d’ouverture du fichier: " + ex.getMessage());
}
// Laissons le temps à l’opération asynchrone de se terminer (dans les applications réelles, utilisez CountDownLatch ou d’autres mécanismes)
Thread.sleep(500);
}
}
Que se passe-t-il ici ?
- Si le fichier n’existe pas, la méthode failed sera appelée avec l’exception correspondante (NoSuchFileException).
- Si l’opération s’est terminée avec succès — completed sera déclenchée.
Exemples d’erreurs typiques
- Fichier introuvable: NoSuchFileException
- Accès refusé: AccessDeniedException
- Erreur de lecture/écriture: diverses sous-classes de IOException
- Problèmes de tampon: BufferOverflowException, BufferUnderflowException
Journalisation et information de l’utilisateur
Une erreur dans un callback asynchrone n’est pas une raison de paniquer, mais ce n’est pas non plus une raison de faire comme si de rien n’était. Une bonne pratique consiste à journaliser l’erreur (par exemple via Logger), et si c’est important pour l’utilisateur — afficher un message ou invoquer un gestionnaire dans l’UI.
Exemple de journalisation :
@Override
public void failed(Throwable exc, ByteBuffer attachment) {
System.err.println("Erreur d’opération asynchrone: " + exc);
exc.printStackTrace();
}
En production, utilisez de vrais loggers (par exemple, java.util.logging ou Log4j), pas System.err.
2. Annulation des opérations asynchrones
Quand l’annulation d’une opération peut-elle être nécessaire ?
Parfois, il faut arrêter une tâche asynchrone en plein milieu. Par exemple, l’utilisateur a changé d’avis et a cliqué sur « Annuler » pendant le téléchargement d’un fichier. Ou la fenêtre de l’application s’est fermée, et l’opération n’a plus de sens. Il arrive aussi qu’à l’arrêt de l’application, il faille simplement libérer proprement les ressources.
Pour ces cas, les E/S asynchrones en Java prennent en charge l’annulation via l’interface Future. Avec celle-ci, vous pouvez interrompre une tâche en cours à tout moment et éviter de gaspiller des ressources.
Comment annuler une opération avec Future ?
La méthode read ou write de AsynchronousFileChannel renvoie un objet Future<Integer>. Cet objet propose la méthode cancel(boolean mayInterruptIfRunning).
Exemple: annulation d’une lecture asynchrone
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);
// Attendons un peu, puis annulons l’opération
Thread.sleep(100);
boolean cancelled = future.cancel(true);
if (cancelled) {
System.out.println("L’opération de lecture a été annulée !");
} else {
System.out.println("Impossible d’annuler l’opération (elle est peut-être déjà terminée)");
}
}
}
}
Points importants :
- L’annulation ne fonctionne que pour les opérations qui ne sont pas encore terminées.
- Si l’opération est déjà terminée — vous ne pourrez pas l’annuler.
- Après l’annulation, lors d’un appel à get() sur ce Future, une exception CancellationException sera levée.
Quand n’est-il plus possible d’annuler une opération ?
Si la tâche s’est déjà terminée — avec succès ou avec une erreur — il n’est plus possible de l’arrêter; le train est déjà parti.
En outre, toutes les implémentations ne savent pas réellement interrompre les opérations au niveau du système d’exploitation. Par exemple, avec certains systèmes de fichiers, « l’annulation » sera purement symbolique: l’opération continuera de s’exécuter, mais vous ignorerez simplement le résultat.
3. Pratique: gestion des erreurs et annulation
Exemple 1: gestion d’une erreur lors de la lecture d’un fichier inexistant
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("L’opération s’est terminée avec succès");
}
@Override
public void failed(Throwable exc, ByteBuffer attachment) {
System.out.println("Erreur lors de la lecture du fichier: " + exc.getClass().getSimpleName() + " - " + exc.getMessage());
}
});
} catch (IOException ex) {
System.out.println("Erreur lors de l’ouverture du fichier: " + ex.getMessage());
}
Thread.sleep(500);
}
}
Que verra-t-on dans la console ?
Erreur lors de l’ouverture du fichier: no_such_file.txt
ou, si l’erreur survient lors de la lecture et non de l’ouverture:
Erreur lors de la lecture du fichier: NoSuchFileException - no_such_file.txt
Exemple 2: annulation d’une opération longue et terminaison correcte
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 Mo
try (AsynchronousFileChannel channel = AsynchronousFileChannel.open(path, StandardOpenOption.READ)) {
Future<Integer> future = channel.read(buffer, 0);
// Après 50 ms, on annule l’opération (pour l’expérience)
Thread.sleep(50);
boolean cancelled = future.cancel(true);
if (cancelled) {
System.out.println("L’opération de lecture a été annulée !");
} else {
System.out.println("Impossible d’annuler l’opération (probablement déjà terminée)");
}
try {
// Essayons d’obtenir le résultat (lèvera CancellationException)
future.get();
} catch (java.util.concurrent.CancellationException ex) {
System.out.println("CancellationException capturée: l’opération est bien annulée.");
}
}
}
}
4. Bonnes pratiques: comment bien faire
Libérez les ressources même en cas d’erreurs
Utilisez try-with-resources pour fermer automatiquement les canaux :
try (AsynchronousFileChannel channel = /* open channel */ null) {
// ...
}
Si vous utilisez CompletionHandler, n’oubliez pas de fermer le canal lorsque toutes les opérations sont terminées. C’est particulièrement important si vous effectuez plusieurs opérations asynchrones à la suite.
Ne bloquez pas l’UI/le thread principal
Les opérations asynchrones servent à ne pas bloquer le thread principal. N’appelez pas future.get() dans le thread UI — sinon l’asynchronisme perd tout son sens.
Journalisez toutes les erreurs
Dans CompletionHandler, implémentez toujours la méthode failed et journalisez (ou propagez) toutes les exceptions.
Vérifiez l’achèvement de toutes les opérations avant la fin du programme
Si le programme se termine avant la fin de l’opération, le résultat peut être perdu. Pour des démonstrations en console, il arrive d’utiliser Thread.sleep(500), mais dans les applications réelles, utilisez CountDownLatch, CompletableFuture ou d’autres mécanismes de synchronisation.
N’oubliez pas l’annulation
Si l’opération n’est plus nécessaire (par exemple, l’utilisateur a fermé la fenêtre), annulez-la via Future.cancel. Cela économisera des ressources et accélérera la réactivité de l’application.
5. Erreurs courantes lors de la gestion des erreurs et de l’annulation en async IO
Erreur n° 1: ignorer la méthode failed dans CompletionHandler.
Si vous ne mettez pas en place la gestion des erreurs, votre application se comportera de manière imprévisible: les erreurs se « perdront », et l’utilisateur ne saura pas pourquoi rien ne se passe.
Erreur n° 2: canal non fermé après la fin des opérations.
Vous avez oublié de fermer AsynchronousFileChannel — vous aurez une fuite de ressources et, éventuellement, un verrouillage du fichier dans l’OS.
Erreur n° 3: attendre le résultat d’une opération asynchrone dans le thread principal.
Vous avez appelé future.get() dans le thread UI — l’interface s’est figée, et tout l’asynchronisme n’a plus de sens.
Erreur n° 4: tenter d’annuler une opération déjà terminée.
Vous avez appelé cancel() trop tard — l’opération était déjà terminée, l’annulation ne fonctionnera pas. Ce n’est pas critique, mais cela peut prêter à confusion pendant le débogage.
Erreur n° 5: ne pas vérifier le résultat de l’annulation.
Vous avez appelé cancel(), mais vous n’avez pas vérifié la valeur de retour et vous n’avez pas géré CancellationException lors de l’appel de get() — l’application peut planter ou se comporter de façon étrange.
Erreur n° 6: ne pas libérer les ressources en cas d’erreur ou d’annulation.
Si vous ne fermez pas le canal après une erreur ou une annulation, des fuites ou des verrous de fichiers peuvent survenir.
GO TO FULL VERSION