Превратите будущее Java в CompletableFuture


97

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

Очевидно, проблема с этим подходом состоит в том, что для каждого Future поток будет заблокирован, чтобы ждать результата Future, что противоречит идее Futures. В некоторых случаях можно было бы добиться большего. Однако в целом без активного ожидания результата в Будущем нет решения .


1
Ха, это именно то, что я написал, прежде чем подумать, что должен быть лучший способ. Но, думаю, нет
— Дэн Мидвуд

12
Хммм ... разве это решение не съедает одну из нитей "общего пула", просто для ожидания? Эти потоки "общего пула" никогда не должны блокироваться ... хммм ...
— Пети

1
@Peti: Ты прав. Однако суть в том, что если вы, скорее всего, делаете что-то неправильно, независимо от того, используете ли вы общий пул или неограниченный пул потоков .
— nosid 04

4
Это может быть не идеально, но использование CompletableFuture.supplyAsync(supplier, new SinglethreadExecutor())по крайней мере не блокирует потоки общего пула.
— MikeFHay

6
Пожалуйста, просто никогда не делайте этого
— Laymain

56

Если библиотека, которую вы хотите использовать, также предлагает метод стиля обратного вызова в дополнение к стилю 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.


1
Я использую асинхронную библиотеку Apache Http, которая принимает FutureCallback. Это облегчило мне жизнь :)
— Абхишек Гаяквад

14

Если ваш 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>в 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получает Supplier. Код не будет компилироваться, если вы попытаетесь передать файл Callable.
— Мартин Андерссон,

@MartinAndersson, верно, спасибо. Я редактировал дальше, чтобы преобразовать a Callable<T>в Supplier<T>.
— Матье

10

Я опубликовал небольшой проект будущего, который пытается сделать лучше, чем простой способ ответа.

Основная идея состоит в том, чтобы использовать только один поток (и, конечно же, не только с циклом вращения) для проверки всех состояний Futures внутри, что помогает избежать блокировки потока из пула для каждого преобразования Future -> CompletableFuture.

Пример использования:

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

Это выглядит интересно. Он использует поток таймера? Почему это не принятый ответ?
— Кира

@Kira Да, он в основном использует один поток таймера для ожидания всех отправленных фьючерсов.
— Дмитрий Спихальский,

8

Предложение:

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-ы из java.util.concurrent.
— Валерий Силаев
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.