Chuyển đổi tương lai Java thành một hoàn chỉnh


92

Java 8 giới thiệu CompletableFuture, một triển khai mới của Future có thể kết hợp (bao gồm một loạt các phương thức thenXxx). Tôi muốn sử dụng điều này độc quyền, nhưng nhiều thư viện tôi muốn sử dụng chỉ trả về các phiên bản không thể tổng Futurehợp.

Có cách nào để tổng Futurehợp các phiên bản đã trả về bên trong CompleteableFutuređể tôi có thể soạn nó không?

Câu trả lời:


56

Có một cách, nhưng bạn sẽ không thích nó. Phương thức sau đây biến a Future<T>thành 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();
    }
  });
}

Rõ ràng, vấn đề với cách tiếp cận này là đối với mỗi Tương lai , một chuỗi sẽ bị chặn để chờ kết quả của Tương lai - loại bỏ ý tưởng về tương lai. Trong một số trường hợp, có thể làm tốt hơn. Tuy nhiên, nhìn chung, không có giải pháp nào mà không chủ động chờ đợi kết quả của Tương lai .


1
Ha, đó chính xác là những gì tôi đã viết trước khi nghĩ rằng phải có một cách tốt hơn. Nhưng, tôi đoán là không
Dan Midwood,

12
Hmmm ... giải pháp này không ăn một trong những chủ đề của "bể chung", chỉ để chờ đợi? Các chủ đề "nhóm chung" đó sẽ không bao giờ bị chặn ... hmmmm ...
Peti

1
@Peti: Bạn nói đúng. Tuy nhiên, vấn đề là, nếu bạn rất có thể đang làm gì đó sai, bất kể bạn đang sử dụng nhóm chung hay nhóm luồng không bị ràng buộc .
nosid

4
Nó có thể không hoàn hảo, nhưng việc sử dụng CompletableFuture.supplyAsync(supplier, new SinglethreadExecutor())ít nhất sẽ không chặn các luồng hồ bơi chung.
MikeFHay

6
Xin vui lòng, chỉ không bao giờ làm điều đó
Laymain

55

Nếu thư viện bạn muốn sử dụng cũng cung cấp phương thức kiểu gọi lại ngoài kiểu Tương lai, bạn có thể cung cấp cho nó một trình xử lý hoàn thành CompletableFuture mà không có bất kỳ chặn luồng bổ sung nào. Như vậy:

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

Nếu không có lệnh gọi lại, cách duy nhất tôi thấy để giải quyết vấn đề này là sử dụng vòng lặp thăm dò đặt tất cả các Future.isDone()kiểm tra của bạn trên một chuỗi duy nhất và sau đó gọi hoàn tất bất cứ khi nào Tương lai là gettable.


Tôi đang sử dụng thư viện không đồng bộ Apache Http chấp nhận FutureCallback. Nó làm cho cuộc sống của tôi dễ dàng :)
Abhishek Gayakwad

11

Nếu của bạn Futurelà kết quả của một lệnh gọi đến một ExecutorServicephương thức (ví dụ submit()), thì cách dễ nhất là sử dụng CompletableFuture.runAsync(Runnable, Executor)phương thức thay thế.

Từ

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

đến

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

Sau CompletableFutuređó được tạo "nguyên bản".

CHỈNH SỬA: Theo đuổi nhận xét của @SamMefford được sửa bởi @MartinAndersson, nếu bạn muốn chuyển a Callable, bạn cần gọi supplyAsync(), chuyển đổi Callable<T>thành a Supplier<T>, ví dụ: với:

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

Bởi vì T Callable.call() throws Exception;ném một ngoại lệ và T Supplier.get();không, bạn phải nắm bắt ngoại lệ để các nguyên mẫu tương thích.


1
Hoặc, nếu bạn đang sử dụng Callable <T> thay vì Runnable, hãy thử cung cấpAsync:CompletableFuture<T> future = CompletableFuture.supplyAsync(myCallable, myExecutor);
Sam Mefford

@SamMefford cảm ơn, tôi đã chỉnh sửa để đưa vào thông tin đó.
Matthieu

supplyAsyncnhận được a Supplier. Mã sẽ không biên dịch nếu bạn cố gắng chuyển vào Callable.
Martin Andersson

@MartinAndersson đúng vậy, cảm ơn. Tôi đã chỉnh sửa thêm để chuyển đổi a Callable<T>thành a Supplier<T>.
Matthieu

10

Tôi đã xuất bản một dự án tương lai nhỏ để cố gắng làm tốt hơn cách nói đơn giản trong câu trả lời.

Ý tưởng chính là sử dụng một chuỗi duy nhất (và tất nhiên không chỉ với một vòng quay) để kiểm tra tất cả các trạng thái của Futures bên trong, điều này giúp tránh chặn một chuỗi từ một nhóm cho mỗi lần chuyển đổi Future -> CompletableFuture.

Ví dụ sử dụng:

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

Điều này có vẻ thú vị. Nó đang sử dụng một chuỗi hẹn giờ? Tại sao đây không phải là câu trả lời được chấp nhận?
Kira

@Kira Vâng, về cơ bản nó sử dụng một chuỗi hẹn giờ để chờ tất cả các hợp đồng tương lai đã gửi.
Dmitry Spikhalskiy

7

Gợi ý:

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

Nhưng, về cơ bản:

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

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

Và, Chương trình Hoàn thành:

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

Thí dụ:

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

điều này khá đơn giản để giải thích về & thanh lịch & phù hợp với hầu hết các trường hợp sử dụng. Tôi sẽ tạo CompletablePromiseContext không tĩnh và lấy tham số cho khoảng thời gian kiểm tra (được đặt thành 1 ms ở đây) sau đó nạp chồng hàm CompletablePromise<V>tạo để có thể cung cấp CompletablePromiseContextkhoảng thời gian kiểm tra có thể khác (lâu hơn) của riêng bạn để chạy lâu dài Future<V>mà bạn không 'không phải hoàn toàn có thể chạy gọi lại (hoặc soạn thư) ngay sau khi kết thúc, và bạn cũng có thể có một ví dụ CompletablePromiseContextđể xem một tập hợp Future(trong trường hợp bạn có nhiều)
Dexter Legaspi

5

Hãy để tôi đề xuất một tùy chọn khác (hy vọng là tốt hơn): https://github.com/vsilaev/java-async-await/tree/master/com.farata.lang.async.examples/src/main/java/com/farata / đồng thời

Tóm lại, ý tưởng như sau:

  1. CompletableTask<V>Giao diện giới thiệu - liên minh của CompletionStage<V>+RunnableFuture<V>
  2. Warp ExecutorServiceđể trả về CompletableTasktừ submit(...)các phương thức (thay vì Future<V>)
  3. Xong, chúng ta đã có thể chạy VÀ tương lai có thể ghép được.

Việc triển khai sử dụng một triển khai CompletionStage thay thế (chú ý, CompletionStage chứ không phải là CompletableFuture):

Sử dụng:

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
Cập nhật nhỏ: mã được chuyển sang dự án riêng biệt, github.com/vsilaev/tascalate-concurrent và giờ đây có thể sử dụng hộp tiện ích Executor-s từ java.util.concurrent.
Valery Silaev
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.