How to make a future that gets completed when any of the given CompletableFutures is completed with a result that matches a certain predicate?

时间秒杀一切 提交于 2019-12-22 12:18:37

问题


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() returns true

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.


回答1:


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.




回答2:


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



回答3:


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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!