Java Geleceğini Tamamlanabilir Bir Geleceğe Dönüştürün


97

Java 8 CompletableFuture, bir araya getirilebilir yeni bir Future uygulaması (bir grup thenXxx yöntemi içerir) sunar. Bunu özel olarak kullanmak istiyorum, ancak kullanmak istediğim kitaplıkların çoğu yalnızca bir araya getirilemeyen Futureörnekleri döndürüyor .

FutureA'nın içinde döndürülen örnekleri CompleteableFutureoluşturup onu oluşturabilmem için bir yol var mı?

Yanıtlar:


57

Bir yolu var ama hoşuna gitmeyecek. Aşağıdaki yöntem, bir dönüştüren Future<T>bir içine 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();
    }
  });
}

Açıkçası, bu yaklaşımla ilgili sorun, her biri için Gelecek için , bir iş parçacığının Geleceğin sonucunu beklemesinin engellenmesidir - gelecek fikriyle çelişir. Bazı durumlarda daha iyisini yapmak mümkün olabilir. Ancak genel olarak Geleceğin sonucunu aktif olarak beklemeden çözüm yoktur .


1
Ha, daha iyi bir yol olması gerektiğini düşünmeden önce yazdığım şey tam olarak buydu. Ama sanırım hayır
— Dan Midwood

12
Hmmm ... bu çözüm "ortak havuz" un iplerinden birini sadece beklemek için yemiyor mu? Bu "ortak havuz" konuları asla engellememelidir ... hmmmm ...
— Peti

1
@Peti: Haklısın. Ancak, önemli olan nokta, ortak havuzu veya sınırsız bir iş parçacığı havuzu kullanıp kullanmadığınıza bakılmaksızın, büyük olasılıkla yanlış bir şey yapıyorsanız .
— nosid

4
Mükemmel olmayabilir, ancak kullanmak CompletableFuture.supplyAsync(supplier, new SinglethreadExecutor())en azından ortak havuz iş parçacıklarını engellemeyecektir.
— MikeFHay

6
Lütfen bunu asla yapma
— Laymain

56

Kullanmak istediğiniz kitaplık, Gelecek stiline ek olarak bir geri arama stili yöntemi de sunuyorsa, ona herhangi bir ek iş parçacığı engellemesi olmadan CompletableFuture'u tamamlayan bir işleyici sağlayabilirsiniz. Şöyle:

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

Geri arama olmadan bunu çözmenin tek yolu, tüm Future.isDone()kontrollerinizi tek bir iş parçacığına koyan ve daha sonra bir Future elde edilebilir olduğunda tam olarak çağıran bir yoklama döngüsü kullanmaktır .


1
FutureCallback'i kabul eden Apache Http async kitaplığını kullanıyorum. Hayatımı kolaylaştırdı :)
— Abhishek Gayakwad

14

Sizin Future, bir ExecutorServiceyönteme yapılan bir çağrının sonucuysa (örn. submit()), En kolayı,CompletableFuture.runAsync(Runnable, Executor) onun yerine yöntemi kullanmaktır.

Nereden

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

-e

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

Daha CompletableFuturesonra "yerel olarak" oluşturulur.

DÜZENLEME: @SamMefford yaparak yorumları edinerek bir geçirmek istiyorsanız, @MartinAndersson düzeltilebilir Callable, çağrıya gerek supplyAsync()dönüştürerek, Callable<T>bir içine Supplier<T>ile örneğin:

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

Çünkü T Callable.call() throws Exception;bir istisna atar ve T Supplier.get();yapmaz, istisnayı yakalamanız gerekir, böylece prototipler uyumlu olur.


1
Veya Runnable yerine Callable <T> kullanıyorsanız, bunun yerine supplyAsync'i deneyin:CompletableFuture<T> future = CompletableFuture.supplyAsync(myCallable, myExecutor);
— Sam Mefford

@SamMefford teşekkürler, bu bilgiyi dahil etmek için düzenledim.
— Matthieu

supplyAsyncbir alır Supplier. Bir Callable.
— Martin Andersson

@MartinAndersson bu doğru, teşekkürler. A'yı Callable<T>bir Supplier<T>.
— Matthieu

10

Cevaptaki basit yoldan daha iyi hale getirmeye çalışan küçük bir gelecek projesi yayınladım .

Ana fikir, içerideki tüm Futures durumlarını kontrol etmek için tek bir iş parçacığı (ve tabii ki yalnızca bir döndürme döngüsü olmadan) kullanmaktır; bu, her bir Future -> CompletableFuture dönüşümü için bir havuzdan bir iş parçacığının engellenmesini önlemeye yardımcı olur.

Kullanım örneği:

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

Bu ilginç görünüyor. Bir zamanlayıcı ipliği kullanıyor mu? Neden bu kabul edilen cevap değil?
— Kira

@Kira Evet, gönderilen tüm vadeli işlemleri beklemek için temelde bir zamanlayıcı iş parçacığı kullanır.
— Dmitry Spikhalskiy

8

Öneri:

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

Ama temelde:

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

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

Ve Tamamlanabilir Söz:

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

Misal:

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

bu oldukça basit ve zariftir ve çoğu kullanım durumuna uyar. CompletablePromiseContext Statik olmayan yapardım ve kontrol aralığı için param alırdım (burada 1 ms olarak ayarlanmıştır), sonra yapıcıyı , yapmadığınız uzun süreli çalışma için muhtemelen farklı (daha uzun) bir kontrol aralığı CompletablePromise<V>sağlayabilmek için aşırı yüklerdim. Bitirdikten hemen sonra kesinlikle geri aramayı çalıştırmanız (veya yazmanız) gerekmez ve ayrıca bir dizi (çok varsa) izlemek için bir örneğiniz olabilirCompletablePromiseContextFuture<V>CompletablePromiseContextFuture
— Dexter Legaspi

5

Başka (umarım, daha iyi) bir seçenek önereyim: https://github.com/vsilaev/java-async-await/tree/master/com.farata.lang.async.examples/src/main/java/com/farata /eşzamanlı

Kısaca fikir şudur:

  1. CompletableTask<V>Arayüzü tanıtın - CompletionStage<V>+RunnableFuture<V>
  2. Çözgü ExecutorServicedönüş CompletableTaskgelen submit(...)yöntemler (yerine Future<V>)
  3. Bitti, koşabilir VE birleştirilebilir Vadeli İşlemlerimiz var.

Uygulama, alternatif bir CompletionStage uygulaması kullanır (dikkat edin, CompletableFuture yerine CompletionStage ):

Kullanım:

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
Küçük güncelleme: kod, github.com/vsilaev/tascalate-concurrent adlı ayrı bir projeye taşınır ve artık java.util.concurrent adresindeki out-of-the- off box Executor- s'leri kullanmak mümkündür.
— Valery Silaev
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.