Transformez Java Future en un CompletableFuture


92

Java 8 introduit CompletableFuture, une nouvelle implémentation de Future qui est composable (inclut un tas de méthodes thenXxx). Je voudrais l'utiliser exclusivement, mais la plupart des bibliothèques que je souhaite utiliser ne renvoient que des Futureinstances non composables .

Existe-t-il un moyen d'envelopper une Futureinstance retournée dans un CompleteableFutureafin que je puisse le composer?

Réponses:


56

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

public static <T> CompletableFuture<T> makeCompletableFuture(Future<T> future) {
  if (future.isDone())
    return transformDoneFuture(future);
  return CompletableFuture.supplyAsync(() -> {
    try {
      if (!future.isDone())
        awaitFutureIsDoneInForkJoinPool(future);
      return future.get();
    } catch (ExecutionException e) {
      throw new RuntimeException(e);
    } catch (InterruptedException e) {
      // Normally, this should never happen inside ForkJoinPool
      Thread.currentThread().interrupt();
      // Add the following statement if the future doesn't have side effects
      // future.cancel(true);
      throw new RuntimeException(e);
    }
  });
}

private static <T> CompletableFuture<T> transformDoneFuture(Future<T> future) {
  CompletableFuture<T> cf = new CompletableFuture<>();
  T result;
  try {
    result = future.get();
  } catch (Throwable ex) {
    cf.completeExceptionally(ex);
    return cf;
  }
  cf.complete(result);
  return cf;
}

private static void awaitFutureIsDoneInForkJoinPool(Future<?> future)
    throws InterruptedException {
  ForkJoinPool.managedBlock(new ForkJoinPool.ManagedBlocker() {
    @Override public boolean block() throws InterruptedException {
      try {
        future.get();
      } catch (ExecutionException e) {
        throw new RuntimeException(e);
      }
      return true;
    }
    @Override public boolean isReleasable() {
      return future.isDone();
    }
  });
}

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


1
Ha, c'est exactement ce que j'ai écrit avant de penser qu'il doit y avoir un meilleur moyen. Mais, je suppose que non
Dan Midwood

12
Hmmm ... cette solution ne mange-t-elle pas l'un des fils du "pool commun", juste pour attendre? Ces threads de "pool commun" ne devraient jamais bloquer ... hmmmm ...
Peti

1
@Peti: Vous avez raison. Cependant, le fait est que si vous faites probablement quelque chose de mal, que vous utilisiez le pool commun ou un pool de threads illimité .
nosid

4
Ce n'est peut-être pas parfait, mais l'utilisation CompletableFuture.supplyAsync(supplier, new SinglethreadExecutor())ne bloquerait au moins pas les threads communs du pool.
MikeFHay

6
S'il vous plaît, ne faites jamais ça
Laymain

55

Si la bibliothèque que vous souhaitez utiliser propose également une méthode de style de rappel en plus du style Future, vous pouvez lui fournir un gestionnaire qui complète 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 façon que je vois résoudre ce problème est d'utiliser une boucle d'interrogation qui place toutes vos Future.isDone()vérifications sur un seul thread, puis d'appeler complete chaque fois qu'un Future est geable.


J'utilise la bibliothèque asynchrone Apache Http qui accepte FutureCallback. Ça m'a rendu la vie facile :)
Abhishek Gayakwad

11

Si votre Futureest le résultat d'un appel à une ExecutorServiceméthode (par exemple submit()), le plus simple serait d'utiliser leCompletableFuture.runAsync(Runnable, Executor) méthode à la place.

De

Runnbale myTask = ... ;
Future<?> future = myExecutor.submit(myTask);

à

Runnbale myTask = ... ;
CompletableFuture<?> future = CompletableFuture.runAsync(myTask, myExecutor);

le CompletableFuture est alors créé "nativement".

EDIT: Poursuite des commentaires de @SamMefford corrigés par @MartinAndersson, si vous voulez passer a Callable, vous devez appeler supplyAsync(), en convertissant le Callable<T>en a Supplier<T>, par exemple avec:

CompletableFuture.supplyAsync(() -> {
    try { return myCallable.call(); }
    catch (Exception ex) { throw new RuntimeException(ex); } // Or return default value
}, myExecutor);

Parce que T Callable.call() throws Exception;lève une exception et T Supplier.get();ne le fait pas, vous devez intercepter l'exception pour que les prototypes soient compatibles.


1
Ou, si vous utilisez Callable <T> plutôt que Runnable, essayez à la place supplyAsync:CompletableFuture<T> future = CompletableFuture.supplyAsync(myCallable, myExecutor);
Sam Mefford

@SamMefford merci, j'ai édité pour inclure ces informations.
Matthieu le

supplyAsyncreçoit un Supplier. Le code ne se compilera pas si vous essayez de transmettre un fichier Callable.
Martin Andersson

@MartinAndersson c'est vrai, merci. J'ai modifié davantage pour convertir un Callable<T>en un fichier Supplier<T>.
Matthieu

10

J'ai publié un petit projet de futur qui essaie de faire mieux que la manière simple dans la réponse.

L'idée principale est d'utiliser le seul thread (et bien sûr avec pas seulement 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);

Cela semble intéressant. Utilise-t-il un fil de minuterie? Comment se fait-il que ce ne soit pas la réponse acceptée?
Kira

@Kira Ouais, il utilise essentiellement un thread de minuterie pour attendre tous les futurs soumis.
Dmitry Spikhalskiy

7

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, le 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);
        });
    }
}

c'est assez simple à raisonner sur & élégant et convient à la plupart des cas d'utilisation. Je ferais le CompletablePromiseContext non-statique et prendrais le paramètre pour l'intervalle de vérification (qui est réglé à 1 ms ici) puis surchargerais le CompletablePromise<V>constructeur pour pouvoir fournir le vôtre CompletablePromiseContextavec un intervalle de vérification éventuellement différent (plus long) pour les longues périodes Future<V>où vous ne Vous ne devez absolument pas pouvoir exécuter le rappel (ou composer) immédiatement après avoir terminé, et vous pouvez également avoir une instance de CompletablePromiseContextpour regarder un ensemble de Future(au cas où vous en auriez beaucoup)
Dexter Legaspi

5

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. Introduire l' CompletableTask<V>interface - l'union du CompletionStage<V>+RunnableFuture<V>
  2. Warp ExecutorServicepour revenir CompletableTaskdes submit(...)méthodes (au lieu de Future<V>)
  3. Fait, nous avons des Futures exécutables ET composables.

L'implémentation utilise une autre implémentation CompletionStage (attention, 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); 

2
Petite mise à jour: le code est déplacé vers un projet séparé, github.com/vsilaev/tascalate-concurrent , et il est maintenant possible d'utiliser des Executor-s de boîte à l'écart de java.util.concurrent.
Valery Silaev
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.