Вопрос 18 · Раздел 19

Как отменить выполнение CompletableFuture

CompletableFuture можно отменить методом cancel(), но есть нюансы:

Версии по языкам: English Russian Ukrainian

🟢 Junior Level

CompletableFuture можно отменить методом cancel(), но есть нюансы:

CompletableFuture<String> cf = CompletableFuture.supplyAsync(() -> {
    Thread.sleep(5000);
    return "Done";
});

// Отмена
boolean cancelled = cf.cancel(true);  // true = mayInterruptIfRunning

System.out.println(cf.isCancelled());  // true

Важно: cancel() не всегда останавливает выполнение! Он только устанавливает статус cancelled.


🟡 Middle Level

Как работает cancel

CompletableFuture<String> cf = CompletableFuture.supplyAsync(() -> {
    // Эта задача продолжит выполняться даже после cancel!
    Thread.sleep(5000);
    return "Done";
});

cf.cancel(false);  // не прерывает

// Задача всё ещё выполняется в фоне!

Почему: cancel() устанавливает результат в CancellationException, но не прерывает поток.

Правильная отмена

1. Cooperative cancellation:

AtomicBoolean cancelled = new AtomicBoolean(false);

CompletableFuture<String> cf = CompletableFuture.supplyAsync(() -> {
    for (int i = 0; i < 1000; i++) {
        if (cancelled.get()) {
            throw new CancellationException("Task cancelled");
        }
        // do work
    }
    return "Done";
});

// Отмена
cancelled.set(true);
cf.cancel(false);

2. Timeout:

cf.orTimeout(5, TimeUnit.SECONDS);  // автоотмена через 5 секунд

Типичные ошибки

  1. Ожидание прерывания: ```java // ❌ cancel() не прерывает поток cf.cancel(true); // задача продолжает выполняться

// ✅ Cooperative cancellation if (Thread.currentThread().isInterrupted()) { throw new CancellationException(); }


---

## 🔴 Senior Level

### Internal Implementation

```java
// В отличие от FutureTask, CompletableFuture.cancel(true) пытается
// прервать поток через Thread.interrupt(). Но это работает только
// если задача проверяет Thread.interrupted() или выполняет блокирующую
// операцию, реагирующую на interrupt.
// На практике cooperative cancellation (через volatile flag) — основной подход.

Архитектурные Trade-offs

Подход Плюсы Минусы
cancel() Просто Не прерывает
Cooperative Надёжно Нужно писать код
Timeout Авто Только время

Edge Cases

1. Dependent CFs:

CompletableFuture<String> cf1 = CompletableFuture.supplyAsync(() -> "Hello");
CompletableFuture<String> cf2 = cf1.thenApply(s -> s + " World");

cf1.cancel(false);
// cf2 тоже будет отменён — зависит от cf1

2. Interrupt flag:

// Результат зависит от тайминга. Если задача уже завершилась — cancel()
// вернёт false и isCancelled() будет false. Если задача ещё выполняется
// и реагирует на interrupt — будет true. Thread.sleep() реагирует на interrupt.

// Thread.interrupted() может помочь
CompletableFuture.supplyAsync(() -> {
    while (!Thread.currentThread().isInterrupted()) {
        // work
    }
    throw new CancellationException();
});

Production Experience

public class CancellableTask {
    private volatile boolean cancelled = false;
    private CompletableFuture<String> future;
    
    public CompletableFuture<String> start() {
        future = CompletableFuture.supplyAsync(() -> {
            while (!cancelled) {
                // work
            }
            if (cancelled) {
                throw new CancellationException();
            }
            return "Done";
        });
        return future;
    }
    
    public void cancel() {
        cancelled = true;
        if (future != null) {
            future.cancel(false);
        }
    }
}

Best Practices

// ✅ Cooperative cancellation
AtomicBoolean cancelled = new AtomicBoolean(false);

// ✅ Timeout
cf.orTimeout(5, TimeUnit.SECONDS);

// ✅ Проверка interrupt flag
if (Thread.currentThread().isInterrupted()) throw new CancellationException();

// ❌ Ожидание что cancel() прервёт поток
// ❌ Игнорирование CancellationException

🎯 Шпаргалка для интервью

Обязательно знать:

  • cancel(true) пытается прервать поток через Thread.interrupt(), но на практике не работает
  • Cooperative cancellation (volatile flag / AtomicBoolean) — основной подход
  • orTimeout() — автоматическая отмена по времени
  • При отмене корневого CF все зависимые завершатся с CancellationException
  • Отмена дочерней цепочки НЕ влияет на корневой CF

Частые уточняющие вопросы:

  • cancel(true) прервёт выполнение? — Нет, только устанавливает статус. Задача продолжает работать
  • Как правильно отменить? — Cooperative: AtomicBoolean flag + проверка в цикле + cancel(false)
  • Что с зависимыми CF при отмене? — Завершаются с CancellationException
  • obtrudeValue для отмены? — Нет, obtrudeValue принудительно меняет результат — dangerous, race conditions

Красные флаги (НЕ говорить):

  • «cancel(true) гарантированно останавливает задачу» — только interrupt flag, задача может игнорировать
  • «После cancel() задача сразу завершается» — продолжает выполняться, нужно cooperative cancellation
  • «obtrudeValue — нормальный способ отмены» — нарушает контракт однократного завершения, race conditions

Связанные темы:

  • [[20. Как реализовать timeout для CompletableFuture]]
  • [[26. Можно ли вручную завершить CompletableFuture результатом]]
  • [[19. Можно ли повторно использовать один CompletableFuture в нескольких цепочках]]
  • [[18. Что произойдёт, если в цепочке CompletableFuture возникнет исключение]]