I have a list of CompletableFuture<MyService>
:
List<CompletableFuture<MyService>>
where MyService
is immutable like the following:
public class MyService {
public boolean isAvailable() {
return true; // or false
}
}
I now want a future that is completed when one of the futures:
- is completed; and
- for the
MyService
instance as provided by that future:MyService.isAvailable()
returnstrue
When proceeding, I need to have the MyService
instance that is available. In other words, I want a CompletableFuture<MyService>
which completes when the two conditions are met.
How can I do this? I know I can use CompletableFuture.anyOf(...)
to proceeed when one of the futures completes, but I am unsure how to integrate the second requirement: MyService.isAvailable()
must be true.
Update: I think I understood your problem now. Would it help you to proxy your futures like this?
List<CompletableFuture<MyService>> givenFutures; // wherever they came from
CompletableFuture<MyService>[] myFutures = givenFutures.stream()
.map(future -> {
final CompletableFuture<MyService> futureWithCheck = new CompletableFuture<>();
future.thenAccept(myService -> {
if (myService.isAvailable()) {
futureWithCheck.complete(myService);
}
});
return futureWithCheck;})
.toArray(CompletableFuture[]::new);
// blocking
MyService availableService = (MyService) CompletableFuture.anyOf(myFutures).get();
// do what you want to do with the available service
Update 2: I thought about your question regarding thenCompose
and perhaps the middle part of my solution could be expressed like this:
CompletableFuture<MyService>[] myFutures = givenFutures.stream()
.map(future ->
future.thenCompose(myService ->
myService.isAvailable() ? CompletableFuture.completedFuture(myService) : new CompletableFuture<>()))
.toArray(CompletableFuture[]::new);
Old Answer, for completeness:
(was gonna comment but I have too little reputation)
Looks like you will either have to poll MyService.isAvailable()
repeatedly and complete another CompletableFuture
once this returns true or MyService
will (like Didier L commented) have to return a Future which it keeps track of and completes, once the internal members you mentioned are changed. So for example the setter for each member involved will have to check whether isAvailable()
is true and if so, complete all futures that it handed out before.
In both cases you have to chain the additional Future
with the ones you have. Guavas Futures
has useful methods, but probably CompletableFuture.allOf(…)
will do the job.
You can try something like the following service, where the future gets completed only if the service is available:
public static class MyService {
private String name;
public MyService(String name) {
this.name = name;
}
public String toString() {
return name;
}
public CompletableFuture<MyService> isAvailable() {
CompletableFuture<MyService> future = new CompletableFuture<>();
Executors.newCachedThreadPool().submit(() -> {
boolean available = checkAvailability();
if (available)
future.complete(this);
});
return future;
}
private boolean checkAvailability() {
try {
int ms = new Random().nextInt(1000);
Thread.sleep(ms);
} catch (InterruptedException e) {
}
return new Random().nextBoolean();
}
}
And then use the service like this:
MyService s1 = new MyService("one");
MyService s2 = new MyService("two");
CompletableFuture<MyService> f1 = s1.isAvailable();
CompletableFuture<MyService> f2 = s2.isAvailable();
System.out.println("waiting for the first service to be available...");
CompletableFuture<Object> any = CompletableFuture.anyOf(f1, f2);
System.out.println("and the winner is: " + any.get());
// you can now safely cancel all futures
f1.cancel(true);
f2.cancel(true);
Considering that a CompletableFuture
can only be completed once, you can create one that will be completed by the first future that returns an available service:
List<CompletableFuture<MyService>> myServiceFutures = …
final CompletableFuture<MyService> availableMyServiceFuture = new CompletableFuture<>();
myServiceFutures.forEach(future -> future.thenAccept(myService -> {
if (myService.isAvailable()) {
// call will be ignored if already completed
availableMyServiceFuture.complete(myService);
}
}));
Additionally, you may want this future to complete exceptionally if no service is available:
CompletableFuture.allOf(myServiceFutures.toArray(new CompletableFuture[myServiceFutures.size()]))
.whenComplete((e, r) -> {
// TODO handle failure of individual futures if that can happen
if (myServiceFutures.stream().map(CompletableFuture::join).noneMatch(MyService::isAvailable)) {
availableMyServiceFuture.completeExceptionally(new IllegalStateException("No service is available"));
}
});
来源:https://stackoverflow.com/questions/48445121/how-to-make-a-future-that-gets-completed-when-any-of-the-given-completablefuture