Java FutureをCompletableFutureに変換する


92

Java 8ではCompletableFuture、構成可能なFutureの新しい実装であるが導入されています(thenXxxメソッドの束が含まれています)。これを排他的に使用したいのですが、使用したいライブラリの多くは、合成できないものだけを返しますFutureインスタンスます。

返されたFutureインスタンスをの中にラップして、CompleteableFutureそれを作成できるようにする方法はありますか?

回答:


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の結果を待つためにスレッドがブロックされることです。これは、futuresの概念と矛盾します。場合によっては、より良い結果が得られる可能性があります。ただし、一般的には、未来の結果を積極的に待たなければ解決策はありません。


1
ハ、もっといい方法があるはずだと思う前に書いたのはまさにその通りです。しかし、私はそうは思いません
ダンミッドウッド'25

12
うーん...このソリューションは、待機のためだけに「共通プール」のスレッドの1つを食べませんか?それらの「共通プール」スレッドは決してブロックすべきではありません...うーん...
Peti

1
@ペティ:その通りです。ただし、重要なのは、共通プールを使用しているか、無制限のスレッドプールを使用しているかに関係なく、何か間違っている可能性が高い場合です。
nosid

4
完璧ではないかもしれませんが、使用CompletableFuture.supplyAsync(supplier, new SinglethreadExecutor())しても少なくとも一般的なプールスレッドはブロックされません。
MikeFHay 2017年

6
お願いだから、絶対にしないでください
レイメイン

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が取得可能になるたびにcompleteを呼び出すポーリングループを使用することです。


FutureCallbackを受け入れるApache Http非同期ライブラリを使用しています。それは私の人生を楽にしてくれました:)
Abhishek Gayakwad 2017

11

メソッドのFuture呼び出しExecutorService(などsubmit())の結果である場合は、CompletableFuture.runAsync(Runnable, Executor)代わりにメソッドを使用するのが最も簡単です。

から

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

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

CompletableFutureその後、「ネイティブ」が作成されます。

EDIT:@SamMeffordでコメントを追求あなたが渡したい場合は、@MartinAnderssonによって修正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
それとも、あなたが呼び出し可能<T>ではなくRunnableを、代わりにsupplyAsyncを試みるよりも使用している場合:CompletableFuture<T> future = CompletableFuture.supplyAsync(myCallable, myExecutor);
サムMefford

@SamMeffordありがとう、その情報を含めるように編集しました。
Matthieu

supplyAsyncを受け取りますSupplier。を渡そうとすると、コードはコンパイルされませんCallable
マーティンアンダーソン、

@MartinAnderssonそうです、ありがとう。さらに編集してa Callable<T>をに変換しましたSupplier<T>
Matthieu

10

は簡単な方法よりも良くしようとする小さな未来のプロジェクトを公​​開しましたは答えで。

主なアイデアは、1つのスレッドのみ(もちろん、スピンループだけでなく)を使用して内部のすべてのFutureの状態をチェックすることです。これにより、Future-> CompletableFuture変換ごとにプールからスレッドがブロックされるのを回避できます。

使用例:

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

これは面白いですね。タイマースレッドを使用していますか?なぜこれは受け入れられない答えですか?
キラ

@Kiraええ、基本的に1つのタイマースレッドを使用して、送信されたすべての先物を待機します。
Dmitry Spikhalskiy 2018年

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ミリ秒に設定されている)チェック間隔のためのparamを取るCompletablePromise<V>独自に提供することができるようにコンストラクタをCompletablePromiseContext長時間実行のための異なる可能性(長い)チェック間隔でFuture<V>どこドン終了直後に絶対にコールバック(または作成)を実行できる必要はありません。またCompletablePromiseContextFuture(多数ある場合)のセットを視聴するためのインスタンスを作成することもできます
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. 完了しました。実行可能で構成可能なFutureがあります。

実装は代替の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を使用できるようになりました。
Valery Silaev 2017
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.