CodeGym /Corsi /JAVA 25 SELF /Future, CompletionHandler: gestione del completamento del...

Future, CompletionHandler: gestione del completamento delle operazioni

JAVA 25 SELF
Livello 56 , Lezione 1
Disponibile

1. Future: «Aspetto che sia pronto»

Le operazioni asincrone in Java sono come spedire una lettera: non resti alla buca ad aspettare la risposta, ma vai a fare altro. Quando la lettera arriva, ricevi semplicemente una notifica. Nel codice, però, devi capire che l’operazione è terminata e ottenere il risultato o l’errore. In Java ci sono due approcci: tramite Future e tramite CompletionHandler.

Con Future ottieni una «ricevuta» — una promessa di un risultato futuro. Puoi controllare di tanto in tanto se è pronto oppure attendere il risultato chiamando get().

Con CompletionHandler non devi aspettare: specifichi in anticipo un handler, e verrà chiamato automaticamente — in caso di successo o di errore.

Come funziona?

Quando chiami un metodo asincrono read() o write() su AsynchronousFileChannel, puoi ottenere un oggetto di tipo Future<Integer>. È come un biglietto in una coda elettronica: l’operazione è partita e tu puoi, in qualsiasi momento, chiedere: «Allora, è pronto?».

Firma del metodo

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

oppure

Future<Integer> write(ByteBuffer src, long position)
  • dst — buffer in cui leggere i dati.
  • src — buffer da cui prendere i dati da scrivere.
  • position — posizione nel file per lettura/scrittura.

Esempio: lettura asincrona con Future

Codice che legge in modo asincrono i primi 1024 byte da un file e attende esplicitamente il risultato:

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

            // Avviamo la lettura asincrona
            Future<Integer> future = channel.read(buffer, 0);

            // ... qui si possono fare altre cose mentre il file viene letto

            // Attendiamo il completamento dell'operazione (chiamata bloccante!)
            int bytesRead = future.get(); // Può lanciare InterruptedException, ExecutionException

            System.out.println("Byte letti: " + bytesRead);

            // Si passa alla lettura dal buffer
            buffer.flip();
            while (buffer.hasRemaining()) {
                System.out.print((char) buffer.get());
            }
        } catch (Exception e) {
            System.err.println("Errore di lettura del file: " + e);
        }
    }
}
  • Il metodo future.get() blocca il thread fino al completamento dell’operazione — è come «aspettare al telefono» finché non chiamano.
  • Puoi chiamare future.isDone() per sapere se l’operazione è terminata e solo dopo invocare get().
  • Se l’operazione termina con errore, get() lancia ExecutionException (all’interno — la causa).

Quando è comodo usare Future?

Future è utile quando vuoi avviare un task e poi, quando ti conviene, attendere il risultato: leggere un file, fare una richiesta, calcolare qualcosa in background. L’approccio è comodo anche per avviare più operazioni in parallelo e raccoglierne i risultati in seguito.

Ma se serve vera asincronia senza blocchi e con notifica automatica del completamento, conviene considerare CompletionHandler.

2. CompletionHandler: «Chiamami quando hai finito»

CompletionHandler è un’interfaccia che implementi e passi al metodo read() o write(). Quando l’operazione termina (o si verifica un errore), Java chiamerà il tuo handler. È come lasciare il numero di telefono: «Chiamate quando è pronto».

Firma del metodo

void read(ByteBuffer dst,
          long position,
          A attachment,
          CompletionHandler<Integer, ? super A> handler)
  • dst — buffer per la lettura.
  • position — posizione nel file.
  • attachment — oggetto arbitrario che verrà passato all’handler (può essere null o, per esempio, il nome del file).
  • handler — il tuo handler.

Interfaccia CompletionHandler

public interface CompletionHandler<V, A> {
    void completed(V result, A attachment);
    void failed(Throwable exc, A attachment);
}
  • completed viene chiamato in caso di successo; result — numero di byte, attachment — il tuo oggetto.
  • failed viene chiamato in caso di errore; exc — l’eccezione.

Esempio: lettura asincrona 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("File " + attachment + " letto, byte: " + result);
                buffer.flip();
                while (buffer.hasRemaining()) {
                    System.out.print((char) buffer.get());
                }
                closeChannel();
            }

            @Override
            public void failed(Throwable exc, String attachment) {
                System.err.println("Errore di lettura del file " + attachment + ": " + exc);
                closeChannel();
            }

            private void closeChannel() {
                try {
                    channel.close();
                } catch (IOException e) {
                    System.err.println("Errore di chiusura del canale: " + e);
                }
            }
        });

        // Importante: il metodo main potrebbe terminare prima della fine della lettura, se non si "trattiene" il thread!
        // Nelle applicazioni reali il thread di solito non termina subito (per esempio, server, UI).
        try {
            Thread.sleep(500); // Diamo tempo all'operazione asincrona (solo per l'esempio!)
        } catch (InterruptedException ignored) {}
    }
}

Passiamo a read() un handler anonimo che sa cosa fare dopo il completamento. In completed otteniamo il risultato e lo elaboriamo, mentre in failed reagiamo all’errore. Non dimenticare di chiudere il canale all’interno dell’handler, per non lasciare risorse aperte.

C’è una particolarità: il metodo main può terminare prima che l’operazione asincrona finisca, perciò nell’esempio è stato aggiunto Thread.sleep(500) — solo per vedere il risultato. Nelle applicazioni reali questo trucco di solito non serve.

Quando è preferibile usare CompletionHandler?

CompletionHandler è utile quando serve vera asincronia senza attese e senza blocchi: avvii l’operazione e descrivi cosa fare al termine. È fondamentale per le UI (JavaFX, Swing), per evitare che l’interfaccia si «blocchi», ed è utile sui server — i thread non restano in attesa, ma vengono usati solo quando c’è lavoro da fare.

4. Confronto tra Future e CompletionHandler

Approccio Blocca il thread? Quando usarlo? Esempio di scenario
Future Sì (con get()) Elaborazione sequenziale semplice Copia del file, report
CompletionHandler No Veramente asincrono, UI, server, operazioni parallele Server, GUI, I/O massivo
  • Future — più semplice da capire, ma per ottenere il risultato richiede il blocco.
  • CompletionHandler — un po’ più complesso, ma offre vera asincronia e non blocca i thread.

5. Pratica: scrittura asincrona su file con CompletionHandler

Esempio che scrive asincronamente una stringa in un file e segnala il risultato:

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 = "Ciao, mondo asincrono!";
        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("Scritti con successo " + result + " byte nel file " + attachment);
                try {
                    channel.close();
                } catch (IOException e) {
                    System.err.println("Errore di chiusura del canale: " + e);
                }
            }

            @Override
            public void failed(Throwable exc, Path attachment) {
                System.err.println("Errore di scrittura nel file " + attachment + ": " + exc);
                try {
                    channel.close();
                } catch (IOException e) {
                    System.err.println("Errore di chiusura del canale: " + e);
                }
            }
        });

        // Tratteniamo main per vedere il risultato (solo per l'esempio)
        try {
            Thread.sleep(500);
        } catch (InterruptedException ignored) {}
    }
}
Commenti
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION