Java
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax
-
Section 3: Object-Oriented Java
-
Section 4: Collections Framework
-
Section 5: Exception Handling
-
Section 6: Generics
-
Section 7: Functional Java
-
Section 8: Concurrency
-
Section 9: I/O and NIO
-
Section 10: JVM Internals
-
Section 11: Modern Java Features
-
Section 12: Build Tools and Project Structure
-
Section 13: Testing
-
Section 14: Databases and Persistence
-
Section 15: Networking
-
Section 16: Design and Best Practices
-
Section 17: Reflection and Annotations
-
Section 18: Logging and Diagnostics
-
Section 19: Date, Time, and Internationalization
-
Section 20: Java Platform Module System
-
Section 21: Security in Java
-
Section 22: Advanced Collections and Data Structures
-
Section 23: More Concurrency Patterns
-
Section 24: Compression, Files, and System Integration
-
Section 25: GUI Programming
-
Section 26: Practical Projects
-
Section 27: Data Structures and Algorithms
-
Section 28: Interview and Algorithm Practice
-
Section 29: JSON and Data Interchange
-
Section 30: More Concurrency Utilities
-
Section 31: More Collections and Streams Practice
-
Section 32: More File and System Programming
-
Section 33: Standard Library Deep Dive
-
Section 34: More Practice and Drills
-
Section 35: More Testing and Quality
-
Section 36: More Design Patterns and Architecture
-
Section 37: Career and Ecosystem
-
Section 38: More OOP and Architecture Practice
-
Section 39: More Enterprise Concepts
-
Section 40: Advanced JavaFX
-
Section 41: More Interview Practice
262: Cancelling and Timing Out Async Tasks
Imagine we're building a dashboard for a logistics company. One of the features is a "Real-time Route Optimizer" that hits a heavy external API to calculate the best path for a truck. The problem? Sometimes that API takes ten seconds, sometimes it takes thirty, and sometimes it just hangs. We can't let our entire UI freeze or our thread pool clog up because one external server is having a bad day.
The API that won't stop talking
I'll start with a basic CompletableFuture that simulates this slow API call. I'm using a Thread.sleep here just to mimic that agonizing wait.
CompletableFuture<String> routeTask = CompletableFuture.supplyAsync(() -> {
try {
System.out.println("Calculating route...");
Thread.sleep(10000); // Simulating a 10-second lag
return "Route Optimized: Take Highway 101";
} catch (InterruptedException e) {
System.out.println("Task was interrupted!");
return "Interrupted";
}
});
Now, what if the user clicks "Cancel" after two seconds? I'll try the most obvious approach: calling routeTask.cancel(true). I'm passing true because I want to interrupt the thread if it's already running.
// User clicks cancel after 2 seconds
Thread.sleep(2000);
routeTask.cancel(true);
System.out.println("Cancelled the task.");
When I run this, I notice something frustrating. The console says "Cancelled the task," but then... a few seconds later... "Task was interrupted!" pops up. Wait, if I cancelled it, why did it keep running for a bit? It turns out CompletableFuture.cancel() doesn't actually send a hard signal to the thread to stop immediately in all environments; it mostly just marks the future as cancelled so that anyone waiting for the result gets an exception. It's a bit "polite" for my liking.
Actually stopping the work
To make cancellation actually work, the code inside the async block has to be "interrupt-aware." Since I used Thread.sleep, it actually does respond to interrupts, but if I were doing a heavy while loop calculating coordinates, I'd need to manually check Thread.currentThread().isInterrupted().
But let's be honest: relying on the user to click "Cancel" is a gamble. We need a systemic way to say, "If this takes longer than 3 seconds, I don't care about the result anymore."
Setting a hard deadline
Java 9 introduced orTimeout(), which is exactly what I need here. Instead of manually managing a timer, I can just chain it onto the future. Let's try that.
CompletableFuture<String> routeTaskWithTimeout = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(10000);
return "Route Optimized";
} catch (InterruptedException e) {
return "Interrupted";
}
}).orTimeout(3, TimeUnit.SECONDS); // Hard limit!
I'll wrap this in a try-catch because orTimeout doesn't just return a null; it throws a TimeoutException wrapped in an ExecutionException when you try to get the result. When I run this, the program crashes after exactly 3 seconds. It's efficient, but crashing isn't a great user experience.
A graceful fallback
I don't want an exception; I want a fallback. If the optimizer is too slow, I'll just give the user a "Standard Route" (the cached one) instead of the "Optimized Route." This is where completeOnTimeout() comes in. It's much cleaner because it provides a default value instead of throwing a tantrum.
CompletableFuture<String> finalRouteTask = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(10000);
return "Optimized Route: Via Scenic Bypass";
} catch (InterruptedException e) {
return "Interrupted";
}
}).completeOnTimeout("Standard Route: Via Main Street", 3, TimeUnit.SECONDS);
System.out.println("Result: " + finalRouteTask.join());
Now, when I run this, I wait 3 seconds, and boom: Result: Standard Route: Via Main Street. The original task might still be churning away in the background for a moment, but our main application logic has moved on. We've successfully decoupled our user experience from the instability of the external API.
📋 Practical Task
Build a Fail-Safe Weather Fetcher
You are tasked with creating a WeatherService class. The service should attempt to fetch weather data from a simulated "Slow API" that randomly takes between 1 and 10 seconds to respond.
- Create a method
fetchWeather()that returns aCompletableFuture<String>. - Inside the future, simulate the delay using
Thread.sleep()with a random duration. - Use
completeOnTimeout()to ensure that if the API takes longer than 2 seconds, the method returns the string:"Weather data currently unavailable - using cached data". - In your
mainmethod, call this service and print the result. - Bonus: Add a mechanism to cancel the task manually using
cancel(true)if the result hasn't returned within 1 second, and observe how theInterruptedExceptionis handled.
There are no comments for now.