将Java Future转换为CompletableFuture


92

Java 8引入 CompletableFuture了可组合的Future的新实现(包括一堆thenXxx方法)。我想专门使用此库,但是我要使用的许多库仅返回非组合库Future实例。

有没有一种方法可以将返回的Future实例包装到内,CompleteableFuture以便我可以编写它?

Answers:


56

有一种方法,但是您不喜欢它。以下方法将a Future<T>转换为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();
    }
  });
}

显然,这种方法的问题在于,对于每个Future,都会阻塞线程以等待Future的结果-与Future的想法相矛盾。在某些情况下,可能会做得更好。但是,总的来说,没有积极等待Future的结果就没有解决方案。


1
哈,这正是我在想到必须有更好的方法之前写的。但是,我想不是
Dan Midwood 2014年

12
嗯...这个解决方案不是为了等待而吃掉了“公共池”的线程之一吗?那些“公共池”线程永远不会阻塞……嗯……
Peti

1
@Peti:你是对的。但是,要点是,无论您使用的是公共池还是无界线程池,如果您很可能做错了什么。
nosid

4
这可能并不完美,但是使用CompletableFuture.supplyAsync(supplier, new SinglethreadExecutor())至少不会阻塞公共池线程。
MikeFHay

6
拜托,永远不要那样做
Laymain

55

如果您要使用的库除了提供Future样式之外还提供了回调样式方法,则可以为其提供一个处理程序,该处理程序可以完成CompletableFuture,而不会造成任何额外的线程阻塞。像这样:

    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(...)

没有回调,我认为解决此问题的另一种方法是使用轮询循环,该循环将所有Future.isDone()检查置于单个线程上,然后在获取Future时调用完成。


我正在使用接受FutureCallback的Apache Http异步库。这让我的生活变得轻松:)
Abhishek Gayakwad

11

如果您Future是调用某个ExecutorService方法(例如submit())的结果,那么最简单的CompletableFuture.runAsync(Runnable, Executor)方法是改用该方法。

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

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

CompletableFuture则创建“原生地”。

编辑:通过@SamMefford进行@MartinAndersson纠正,如果要传递a Callable,则需要调用supplyAsync(),将转换Callable<T>Supplier<T>,例如:

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

因为T Callable.call() throws Exception;会引发异常而T Supplier.get();不会引发异常,所以您必须捕获异常,以便原型兼容。


1
或者,如果您使用的是Callable <T>而不是Runnable,请尝试使用supplyAsync:CompletableFuture<T> future = CompletableFuture.supplyAsync(myCallable, myExecutor);
Sam Mefford,

@SamMefford谢谢,我进行了编辑以包括该信息。
Matthieu,

supplyAsync收到Supplier。如果您尝试传入,则代码将无法编译Callable
马丁·安德森

@MartinAndersson是的,谢谢。我进一步编辑将转换Callable<T>Supplier<T>
Matthieu

10

我发布了一个未来的项目,该项目试图比简单的方法做得更好答案中。

主要思想是仅使用一个线程(当然也不仅仅是旋转循环)来检查内部的所有Futures状态,这有助于避免针对每个Future-> CompletableFuture转换从池中阻塞线程。

用法示例:

Future oldFuture = ...;
CompletableFuture profit = Futurity.shift(oldFuture);

这看起来很有趣。是否使用计时器线程?为什么这不是公认的答案?
基拉

@Kira是的,它基本上使用一个计时器线程来等待所有提交的期货。
德米特里·史匹哈斯基

7

建议:

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

但是,基本上:

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

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

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

例:

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

这很容易推断,优雅且适合大多数用例。我将使CompletablePromiseContext 非静态对象并为检查间隔(此处设置为1 ms)获取参数,然后重载CompletablePromise<V>构造函数,以便能够为您自己的设备CompletablePromiseContext提供一个可能不同的(更长)检查间隔,以Future<V>在您不运行的情况下长时间运行不必绝对能够在完成后立即运行回调(或编写),并且您还可以拥有一个实例CompletablePromiseContext来观看一组Future(如果有的话)
Dexter Legaspi

5

让我提出另一个(希望更好)的选择:https : //github.com/vsilaev/java-async-await/tree/master/com.farata.lang.async.examples/src/main/java/com/farata /同时

简而言之,想法如下:

  1. 介绍CompletableTask<V>界面- CompletionStage<V>+ 的并集 RunnableFuture<V>
  2. 从方法ExecutorService返回的扭曲(而不是CompletableTasksubmit(...)Future<V>
  3. 完成,我们拥有可运行且可组合的期货。

实现使用替代的CompletionStage实现(请注意,CompletionStage而不是CompletableFuture):

用法:

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
较小的更新:代码已移至单独的项目github.com/vsilaev/tascalate-concurrent,现在可以使用java.util.concurrent中的杂物箱Executor-s。
Valery Silaev '17
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.