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.