web-dev-qa-db-fra.com

Transformer Java Future en un CompletableFuture

Java 8 introduit CompletableFuture, une nouvelle implémentation de Future composable (inclut un ensemble de méthodes thenXxx). J'aimerais utiliser ceci exclusivement, mais beaucoup de bibliothèques que je veux utiliser ne renvoient que des instances non composables Future.

Existe-t-il un moyen d'envelopper une instance Future renvoyée dans une instance CompleteableFuture afin que je puisse la composer?

73
Dan Midwood

Il y a un moyen, mais vous ne l'aimerez pas. La méthode suivante transforme un Future<T> dans une CompletableFuture<T>:

public static <T> CompletableFuture<T> makeCompletableFuture(Future<T> future) {
    return CompletableFuture.supplyAsync(() -> {
        try {
            return future.get();
        } catch (InterruptedException|ExecutionException e) {
            throw new RuntimeException(e);
        }
    });
}

Évidemment, le problème avec cette approche est que, pour chaque Future, un thread sera bloqué pour attendre le résultat du Future - contredisant l’idée d’avenir. Dans certains cas, il pourrait être possible de faire mieux. Cependant, en général, il n'y a pas de solution sans attendre activement le résultat du Future.

49
nosid

Si la bibliothèque que vous souhaitez utiliser offre également une méthode de style de rappel en plus du style de futur, vous pouvez lui fournir un gestionnaire complétant CompletableFuture sans aucun blocage de thread supplémentaire. Ainsi:

    AsynchronousFileChannel open = AsynchronousFileChannel.open(Paths.get("/some/file"));
    // ... 
    CompletableFuture<ByteBuffer> completableFuture = new CompletableFuture<ByteBuffer>();
    open.read(buffer, position, null, new CompletionHandler<Integer, Void>() {
        @Override
        public void completed(Integer result, Void attachment) {
            completableFuture.complete(buffer);
        }

        @Override
        public void failed(Throwable exc, Void attachment) {
            completableFuture.completeExceptionally(exc);
        }
    });
    completableFuture.thenApply(...)

Sans le rappel, la seule autre solution envisageable consiste à utiliser une boucle d'interrogation qui place toutes vos vérifications Future.isDone() sur un seul thread, puis appelle la commande complete dès qu'un avenir est détectable.

47
Kafkaesque

J'ai publié un petit projet futurity qui tente de faire mieux que méthode simple dans la réponse.

L'idée principale est d'utiliser le seul thread (et bien sûr, pas seulement avec une boucle de rotation) pour vérifier tous les états Futures à l'intérieur, ce qui permet d'éviter de bloquer un thread d'un pool pour chaque transformation Future -> CompletableFuture.

Exemple d'utilisation:

Future oldFuture = ...;
CompletableFuture profit = Futurity.shift(oldFuture);
7
Dmitry Spikhalskiy

Permettez-moi de suggérer une autre option (espérons-le, meilleure): https://github.com/vsilaev/Java-async-await/tree/master/com.farata.lang.async.examples/src/main/Java/com/farata/concurrent

En bref, l’idée est la suivante:

  1. Introduisez l'interface CompletableTask<V> - l'union du CompletionStage<V> + RunnableFuture<V>
  2. Déformer ExecutorService pour renvoyer CompletableTask à partir de submit(...) méthodes (au lieu de Future<V>)
  3. C'est fait, nous avons des Futurs exécutables ET composables.

L'implémentation utilise une implémentation alternative de CompletionStage (soyez attentif, CompletionStage plutôt que CompletableFuture):

Usage:

J8ExecutorService exec = J8Executors.newCachedThreadPool();
CompletionStage<String> = exec
   .submit( someCallableA )
   .thenCombineAsync( exec.submit(someCallableB), (a, b) -> a + " " + b)
   .thenCombine( exec.submit(someCallableC), (ab, b) -> ab + " " + c); 
5
Valery Silaev

Suggestion:

http://www.thedevpiece.com/converting-old-Java-future-to-completablefuture/

Mais fondamentalement:

public class CompletablePromiseContext {
    private static final ScheduledExecutorService SERVICE = Executors.newSingleThreadScheduledExecutor();

    public static void schedule(Runnable r) {
        SERVICE.schedule(r, 1, TimeUnit.MILLISECONDS);
    }
}

Et, la CompletablePromise:

public class CompletablePromise<V> extends CompletableFuture<V> {
    private Future<V> future;

    public CompletablePromise(Future<V> future) {
        this.future = future;
        CompletablePromiseContext.schedule(this::tryToComplete);
    }

    private void tryToComplete() {
        if (future.isDone()) {
            try {
                complete(future.get());
            } catch (InterruptedException e) {
                completeExceptionally(e);
            } catch (ExecutionException e) {
                completeExceptionally(e.getCause());
            }
            return;
        }

        if (future.isCancelled()) {
            cancel(true);
            return;
        }

        CompletablePromiseContext.schedule(this::tryToComplete);
    }
}

Exemple:

public class Main {
    public static void main(String[] args) {
        final ExecutorService service = Executors.newSingleThreadExecutor();
        final Future<String> stringFuture = service.submit(() -> "success");
        final CompletableFuture<String> completableFuture = new CompletablePromise<>(stringFuture);

        completableFuture.whenComplete((result, failure) -> {
            System.out.println(result);
        });
    }
}
5
Gabriel Francisco