JavaRush /Курсы /JAVA 25 SELF /Future, CompletionHandler: обработка завершения операций

Future, CompletionHandler: обработка завершения операций

JAVA 25 SELF
56 уровень , 1 лекция
Открыта

1. Future: «Подожду, когда будет готово»

Асинхронные операции в Java — это как отправить письмо по почте: вы не стоите у ящика и не ждёте ответ, а идёте заниматься своими делами. Когда письмо придёт — вы просто получаете уведомление. В коде же нужно понять, что операция завершилась, и узнать результат или ошибку. В Java есть два подхода: через Future и через CompletionHandler.

С Future вы получаете «квитанцию» — обещание результата в будущем. Можно время от времени проверять, готов ли он, или дождаться результата, вызвав get().

С CompletionHandler можно вовсе не ждать: заранее указываете обработчик, и он вызовется сам — при успехе или ошибке.

Как это работает?

Когда вы вызываете асинхронный метод read() или write() у AsynchronousFileChannel, вы можете получить объект типа Future<Integer>. Это как талончик в электронной очереди: операция началась, а вы можете в любой момент спросить: «Ну что, готово?».

Сигнатура метода

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

или

Future<Integer> write(ByteBuffer src, long position)
  • dst — буфер, куда читать данные.
  • src — буфер, откуда писать данные.
  • position — позиция в файле для чтения/записи.

Пример: Асинхронное чтение с Future

Код, который асинхронно читает первые 1024 байта из файла и явно дожидается результата:

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

            // Запускаем асинхронное чтение
            Future<Integer> future = channel.read(buffer, 0);

            // ... тут можно делать другие дела, пока файл читается

            // Ждём завершения операции (блокирующий вызов!)
            int bytesRead = future.get(); // Может выбросить InterruptedException, ExecutionException

            System.out.println("Прочитано байт: " + bytesRead);

            // Переходим к чтению из буфера
            buffer.flip();
            while (buffer.hasRemaining()) {
                System.out.print((char) buffer.get());
            }
        } catch (Exception e) {
            System.err.println("Ошибка чтения файла: " + e);
        }
    }
}
  • Метод future.get() блокирует поток до завершения операции — это как «ждать у телефона», пока не позвонят.
  • Можно вызвать future.isDone(), чтобы узнать, завершилась ли операция, и только потом вызывать get().
  • Если операция завершилась с ошибкой, get() выбросит ExecutionException (внутри — причина).

Когда удобен Future?

Future хорош, когда вы хотите запустить задачу и затем, когда удобно, дождаться результата: читать файл, сделать запрос, посчитать что-то в фоне. Подход удобен и для запуска нескольких операций параллельно с последующим сбором результатов.

Но если нужна настоящая асинхронность без блокировок и с автоматическим уведомлением о завершении, стоит рассмотреть CompletionHandler.

2. CompletionHandler: «Позови меня, когда закончишь»

CompletionHandler — это интерфейс, который вы реализуете и передаёте в метод read() или write(). Когда операция завершится (или произойдёт ошибка), Java сама вызовет ваш обработчик. Это как оставить номер телефона: «Позвоните, когда будет готово».

Сигнатура метода

void read(ByteBuffer dst,
          long position,
          A attachment,
          CompletionHandler<Integer, ? super A> handler)
  • dst — буфер для чтения.
  • position — позиция в файле.
  • attachment — произвольный объект, который будет передан в обработчик (можно null или, например, имя файла).
  • handler — ваш обработчик.

Интерфейс CompletionHandler

public interface CompletionHandler<V, A> {
    void completed(V result, A attachment);
    void failed(Throwable exc, A attachment);
}
  • completed вызывается при успехе; result — число байт, attachment — ваш объект.
  • failed вызывается при ошибке; exc — исключение.

Пример: Асинхронное чтение с 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("Файл " + attachment + " прочитан, байт: " + result);
                buffer.flip();
                while (buffer.hasRemaining()) {
                    System.out.print((char) buffer.get());
                }
                closeChannel();
            }

            @Override
            public void failed(Throwable exc, String attachment) {
                System.err.println("Ошибка чтения файла " + attachment + ": " + exc);
                closeChannel();
            }

            private void closeChannel() {
                try {
                    channel.close();
                } catch (IOException e) {
                    System.err.println("Ошибка закрытия канала: " + e);
                }
            }
        });

        // Важно: метод main завершится до окончания чтения, если не "придержать" поток!
        // В реальных приложениях поток обычно не завершается сразу (например, сервер, UI).
        try {
            Thread.sleep(500); // Дадим время асинхронной операции (только для примера!)
        } catch (InterruptedException ignored) {}
    }
}

Мы передаём в read() анонимный обработчик, который знает, что делать после завершения. В completed получаем результат и обрабатываем его, а в failed — реагируем на ошибку. Не забудьте закрывать канал внутри обработчика, чтобы не оставить открытые ресурсы.

Есть нюанс: метод main может завершиться раньше, чем закончится асинхронная операция, поэтому в примере добавлен Thread.sleep(500) — только чтобы увидеть результат. В реальных приложениях такой трюк обычно не нужен.

Когда удобнее использовать CompletionHandler?

CompletionHandler хорош, когда нужна настоящая асинхронность без ожиданий и блокировок: вы запускаете операцию и описываете, что делать по завершении. Это критично для UI (JavaFX, Swing), чтобы интерфейс не «вис», и полезно на серверах — потоки не простаивают в ожидании, а используются только при наличии работы.

4. Сравнение Future и CompletionHandler

Подход Блокирует поток? Когда использовать? Пример сценария
Future Да (при get()) Простая последовательная обработка Копирование файла, отчёты
CompletionHandler Нет Truly async, UI, сервер, параллельные операции Сервер, GUI, массовый IO
  • Future — проще для понимания, но для получения результата требуется блокировка.
  • CompletionHandler — чуть сложнее, но даёт настоящую асинхронность и не блокирует потоки.

5. Практика: Асинхронная запись в файл с 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;
import java.nio.charset.StandardCharsets;

public class CompletionHandlerWriteExample {
    public static void main(String[] args) throws IOException {
        String text = "Привет, асинхронный мир!";
        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("Успешно записано " + result + " байт в файл " + attachment);
                try {
                    channel.close();
                } catch (IOException e) {
                    System.err.println("Ошибка закрытия канала: " + e);
                }
            }

            @Override
            public void failed(Throwable exc, Path attachment) {
                System.err.println("Ошибка записи в файл " + attachment + ": " + exc);
                try {
                    channel.close();
                } catch (IOException e) {
                    System.err.println("Ошибка закрытия канала: " + e);
                }
            }
        });

        // Придержим main, чтобы увидеть результат (только для примера)
        try {
            Thread.sleep(500);
        } catch (InterruptedException ignored) {}
    }
}
1
Задача
JAVA 25 SELF, 56 уровень, 1 лекция
Недоступна
Асинхронная проверка начала файла с уведомлением 🕵️‍♀️
Асинхронная проверка начала файла с уведомлением 🕵️‍♀️
1
Задача
JAVA 25 SELF, 56 уровень, 1 лекция
Недоступна
Асинхронная запись телеметрических данных с метаданными 🛰️
Асинхронная запись телеметрических данных с метаданными 🛰️
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ