Перетворіть Java Future в CompletableFuture


95

Java 8 представляє CompletableFutureнову реалізацію Future, яку можна скласти (включає купу методів thenXxx). Я хотів би використовувати це виключно, але багато бібліотек, які я хочу використовувати, повертають лише некомпоновані Futureекземпляри.

Чи є спосіб обернути повернуті Futureекземпляри всередині, CompleteableFutureщоб я міг їх скласти?

Відповіді:


57

Є спосіб, але він вам не сподобається. Наступний метод перетворює 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();
    }
  });
}

Очевидно, що проблема з цим підходом полягає в тому, що для кожного Майбутнього буде заблокована нитка, яка чекатиме результату Майбутнього - суперечить ідеї майбутнього. У деяких випадках це може бути краще. Однак загалом рішення не існує без активного очікування результату Майбутнього .


1
Ха, це саме те, що я писав, перш ніж думати, що повинен бути кращий спосіб. Але, мабуть, ні
Ден Мідвуд

12
Хммм ... хіба це рішення не їсть одну з ниток "загального басейну", лише на очікування? Ці потоки "загального пулу" ніколи не повинні блокувати ... хмммм ...
Петі

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()перевірки в одному потоці, а потім викликає завершені, коли можна отримати майбутнє.


Я використовую асинхронну бібліотеку Apache Http, яка приймає FutureCallback. Це полегшило моє життя :)
Абхішек Гаяквад

13

Якщо ваш 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, якщо ви хочете передати a Callable, вам потрібно зателефонувати supplyAsync(), перетворивши Callable<T>a на 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);
Сем Меффорд,

@SamMefford, дякую, я відредагував цю інформацію.
Матьє

supplyAsyncотримує a Supplier. Код не буде скомпільований, якщо ви спробуєте передати файл Callable.
Мартін Андерссон,

@MartinAndersson це правильно, дякую. Я редагував далі, щоб перетворити a Callable<T>на a Supplier<T>.
Матьє

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 мс), а потім перевантажив CompletablePromise<V>конструктор, щоб мати можливість надати власному CompletablePromiseContextінший (довший) інтервал перевірки для тривалого запуску, Future<V>де ви цього не робите Ви не повинні мати абсолютної можливості запустити зворотний виклик (або скласти) відразу після закінчення, і ви також можете мати екземпляр CompletablePromiseContextдля перегляду набору Future(якщо їх у вас багато)
Декстер Легаспі,

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для повернення CompletableTaskз submit(...)методів (замість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 , і тепер можна використовувати позамежний ящик Executor-s з java.util.concurrent.
Валерій Сілаєв
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.