CodeGym /Cours /JAVA 25 SELF /Future, CompletionHandler : gestion de la fin des op...

Future, CompletionHandler : gestion de la fin des opérations

JAVA 25 SELF
Niveau 56, Leçon 1
Disponible

1. Future : « J’attendrai que ce soit prêt »

Les opérations asynchrones en Java, c’est comme envoyer une lettre par la poste : vous ne restez pas devant la boîte à attendre la réponse, vous vaquez à vos occupations. Quand la lettre arrive — vous recevez simplement une notification. Dans le code, il faut comprendre que l’opération est terminée et récupérer le résultat ou l’erreur. En Java, il existe deux approches : via Future et via CompletionHandler.

Avec Future, vous recevez un « reçu » — une promesse d’un résultat futur. Vous pouvez vérifier de temps en temps s’il est prêt, ou attendre le résultat en appelant get().

Avec CompletionHandler, vous n’avez pas besoin d’attendre : vous fournissez à l’avance un gestionnaire, et il sera appelé automatiquement — en cas de succès ou d’échec.

Comment ça fonctionne ?

Lorsque vous appelez la méthode asynchrone read() ou write() d’AsynchronousFileChannel, vous pouvez obtenir un objet de type Future<Integer>. C’est comme un ticket dans une file d’attente électronique : l’opération a commencé et vous pouvez, à tout moment, demander : « Alors, c’est prêt ? ».

Signature de méthode

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

ou

Future<Integer> write(ByteBuffer src, long position)
  • dst — le tampon dans lequel lire les données.
  • src — le tampon depuis lequel écrire les données.
  • position — la position dans le fichier pour la lecture/écriture.

Exemple : lecture asynchrone avec Future

Un code qui lit de façon asynchrone les premiers 1024 octets d’un fichier et attend explicitement le résultat :

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);

            // On lance la lecture asynchrone
            Future<Integer> future = channel.read(buffer, 0);

            // ... ici on peut faire autre chose pendant que le fichier est lu

            // On attend la fin de l’opération (appel bloquant !)
            int bytesRead = future.get(); // Peut lever InterruptedException, ExecutionException

            System.out.println("Octets lus : " + bytesRead);

            // On passe à la lecture depuis le tampon
            buffer.flip();
            while (buffer.hasRemaining()) {
                System.out.print((char) buffer.get());
            }
        } catch (Exception e) {
            System.err.println("Erreur de lecture du fichier : " + e);
        }
    }
}
  • La méthode future.get() bloque le thread jusqu’à la fin de l’opération — c’est comme « attendre au téléphone » jusqu’à l’appel.
  • Vous pouvez appeler future.isDone() pour savoir si l’opération est terminée, puis seulement appeler get().
  • Si l’opération se termine par une erreur, get() lèvera une ExecutionException (la cause est à l’intérieur).

Quand Future est-il pratique ?

Future est utile lorsque vous voulez lancer une tâche puis, au moment opportun, attendre le résultat : lire un fichier, faire une requête, effectuer un calcul en arrière-plan. L’approche est pratique aussi pour lancer plusieurs opérations en parallèle puis collecter les résultats.

Mais si vous avez besoin d’une vraie asynchronie sans blocage et avec notification automatique d’achèvement, envisagez CompletionHandler.

2. CompletionHandler : « Appelle-moi quand tu auras fini »

CompletionHandler est une interface que vous implémentez et que vous passez à la méthode read() ou write(). Lorsque l’opération se termine (ou qu’une erreur survient), Java appelle automatiquement votre gestionnaire. C’est comme laisser un numéro de téléphone : « Appelez quand ce sera prêt ».

Signature de méthode

void read(ByteBuffer dst,
          long position,
          A attachment,
          CompletionHandler<Integer, ? super A> handler)
  • dst — tampon de lecture.
  • position — position dans le fichier.
  • attachment — un objet arbitraire qui sera passé au gestionnaire (peut être null ou, par exemple, un nom de fichier).
  • handler — votre gestionnaire.

Interface CompletionHandler

public interface CompletionHandler<V, A> {
    void completed(V result, A attachment);
    void failed(Throwable exc, A attachment);
}
  • completed est appelé en cas de succès ; result — le nombre d’octets, attachment — votre objet.
  • failed est appelé en cas d’erreur ; exc — l’exception.

Exemple : lecture asynchrone avec 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("Fichier " + attachment + " lu, octets : " + result);
                buffer.flip();
                while (buffer.hasRemaining()) {
                    System.out.print((char) buffer.get());
                }
                closeChannel();
            }

            @Override
            public void failed(Throwable exc, String attachment) {
                System.err.println("Erreur de lecture du fichier " + attachment + " : " + exc);
                closeChannel();
            }

            private void closeChannel() {
                try {
                    channel.close();
                } catch (IOException e) {
                    System.err.println("Erreur lors de la fermeture du canal : " + e);
                }
            }
        });

        // Important : la méthode main se terminera avant la fin de la lecture si on ne 'retient' pas le thread !
        // Dans les applications réelles, le thread ne se termine généralement pas tout de suite (par exemple, serveur, UI).
        try {
            Thread.sleep(500); // Laissons du temps à l’opération asynchrone (pour l’exemple uniquement !)
        } catch (InterruptedException ignored) {}
    }
}

Nous passons à read() un gestionnaire anonyme qui sait quoi faire après l’achèvement. Dans completed, nous recevons le résultat et le traitons, et dans failed — nous réagissons à l’erreur. N’oubliez pas de fermer le canal à l’intérieur du gestionnaire afin de ne pas laisser de ressources ouvertes.

Il y a un point d’attention : la méthode main peut se terminer avant la fin de l’opération asynchrone, c’est pourquoi Thread.sleep(500) a été ajouté — uniquement pour voir le résultat. Dans les applications réelles, cette astuce n’est généralement pas nécessaire.

Quand est-il préférable d’utiliser CompletionHandler ?

CompletionHandler est idéal lorsque vous avez besoin d’une vraie asynchronie sans attentes ni blocages : vous lancez l’opération et décrivez quoi faire à l’achèvement. C’est crucial pour l’UI (JavaFX, Swing) afin que l’interface ne se « fige » pas, et utile sur les serveurs — les threads ne restent pas inactifs en attente, ils ne sont utilisés que lorsqu’il y a du travail.

4. Comparaison de Future et CompletionHandler

Approche Bloque le thread ? Quand l’utiliser ? Exemple de scénario
Future Oui (lors de get()) Traitement séquentiel simple Copie de fichier, rapports
CompletionHandler Non Vraiment asynchrone, UI, serveur, opérations parallèles Serveur, GUI, IO massif
  • Future — plus simple à comprendre, mais nécessite un blocage pour obtenir le résultat.
  • CompletionHandler — un peu plus complexe, mais fournit une véritable asynchronie et ne bloque pas les threads.

5. Pratique : écriture asynchrone dans un fichier avec CompletionHandler

Un exemple qui écrit de manière asynchrone une chaîne dans un fichier et signale le résultat :

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 = "Bonjour, monde asynchrone !";
        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("Écrit avec succès " + result + " octets dans le fichier " + attachment);
                try {
                    channel.close();
                } catch (IOException e) {
                    System.err.println("Erreur lors de la fermeture du canal : " + e);
                }
            }

            @Override
            public void failed(Throwable exc, Path attachment) {
                System.err.println("Erreur d’écriture dans le fichier " + attachment + " : " + exc);
                try {
                    channel.close();
                } catch (IOException e) {
                    System.err.println("Erreur lors de la fermeture du canal : " + e);
                }
            }
        });

        // On 'retient' main pour voir le résultat (pour l’exemple seulement)
        try {
            Thread.sleep(500);
        } catch (InterruptedException ignored) {}
    }
}
1
Mission
JAVA 25 SELF, niveau 56, leçon 1
Bloqué
Vérification asynchrone du début du fichier avec notification 🕵️‍♀️
Vérification asynchrone du début du fichier avec notification 🕵️‍♀️
1
Mission
JAVA 25 SELF, niveau 56, leçon 1
Bloqué
Écriture asynchrone de données télémétriques avec métadonnées 🛰️
Écriture asynchrone de données télémétriques avec métadonnées 🛰️
Commentaires
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION