Як скасувати виконання CompletableFuture
CompletableFuture можна скасувати методом cancel(), але є нюанси:
🟢 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 секунд
Типові помилки
- Очікування переривання: ```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 виникне виключення]]