Gelecek Listesini Beklerken


145

Ben Listvadeli bir döndüren bir yöntem var

List<Future<O>> futures = getFutures();

Şimdi, tüm vadeli işlemlerin başarıyla işlenmesini veya geleceği tarafından geri döndürülen görevlerin herhangi birini bir istisna atana kadar beklemek istiyorum. Bir görev bir istisna atsa bile, diğer gelecekleri beklemenin bir anlamı yoktur.

Basit bir yaklaşım

wait() {

   For(Future f : futures) {
     try {
       f.get();
     } catch(Exception e) {
       //TODO catch specific exception
       // this future threw exception , means somone could not do its task
       return;
     }
   }
}

Ancak buradaki sorun, örneğin 4. gelecek bir istisna atarsa, ilk 3 geleceğin mevcut olmasını gereksiz yere bekleyeceğim.

Bunu nasıl çözebilirim? Geri sayım mandalı herhangi bir şekilde yardımcı olur mu? isDoneJava doc diyor ki Future’u kullanamıyorum

boolean isDone()
Returns true if this task completed. Completion may be due to normal termination, an exception, or cancellation -- in all of these cases, this method will return true.

1
bu gelecekleri kim üretir? Ne tür? Java.util.concurrent.Future arabirimi istediğiniz işlevselliği sağlamaz, tek yolu kendi Futures'larınızı geri aramalarla kullanmaktır.
Alexei Kaigorodov

ExecutionServiceHer "toplu iş" için bir örnek yapabilir , bunları gönderebilir, sonra derhal hizmeti kapatabilir ve awaitTermination()üzerinde kullanabilirsiniz .
millimoose

Mandalın da azaltılmasını sağlamak için CountDownLatchtüm geleceklerinizin gövdesini bir sarılmışsa kullanabilirsiniz try..finally.
millimoose


İ execureservice bir görev gönderdiğinizde EVET @AlexeiKaigorodov, benim gelecekteki tipi java.util.concurrent.I vardır callable.I olsun Futture geleceği dava am
user93796

Yanıtlar:


124

Gelecekleri hazır olur olmaz almak için bir CompletionService kullanabilirsiniz ve bunlardan biri bir istisna atarsa, işlemi iptal edin. Bunun gibi bir şey:

Executor executor = Executors.newFixedThreadPool(4);
CompletionService<SomeResult> completionService = 
       new ExecutorCompletionService<SomeResult>(executor);

//4 tasks
for(int i = 0; i < 4; i++) {
   completionService.submit(new Callable<SomeResult>() {
       public SomeResult call() {
           ...
           return result;
       }
   });
}

int received = 0;
boolean errors = false;

while(received < 4 && !errors) {
      Future<SomeResult> resultFuture = completionService.take(); //blocks if none available
      try {
         SomeResult result = resultFuture.get();
         received ++;
         ... // do something with the result
      }
      catch(Exception e) {
             //log
         errors = true;
      }
}

Biri hata atarsa, hala yürütülmekte olan görevleri iptal etmek için daha da geliştirebileceğinizi düşünüyorum.


1
: Kodunuz benim yazımda bahsettiğim aynı konuya sahiptir.Gelecekte istisna atarsa, kod hala gelecekteki 1,2,3'ün tamamlanmasını bekler. ya da completionSerice.take) ilk tamamlayan geleceği geri getirecek mi?
user93796

1
Tamamlama hizmetinden en fazla X saniye beklemesini söyleyebilir miyim?
user93796

1
Olmamalı. Gelecekleri yinelemez, ancak hazır olur olmaz istisna atılmazsa işlenir / doğrulanır.
dcernahoschi

2
Bir geleceğin kuyrukta görünmesini bekleyen zaman aşımı için üzerinde bir anket (saniye) yöntemi vardır CompletionService.
dcernahoschi

İşte github üzerinde çalışan örnek: github.com/princegoyal1987/FutureDemo
user18853

107

Java 8 kullanıyorsanız , geri çağrıyı yalnızca verilen tüm CompletableFutures yapıldıktan sonra uygulayan CompletableFuture ve CompletableFuture.allOf ile bunu daha kolay yapabilirsiniz .

// Waits for *all* futures to complete and returns a list of results.
// If *any* future completes exceptionally then the resulting future will also complete exceptionally.

public static <T> CompletableFuture<List<T>> all(List<CompletableFuture<T>> futures) {
    CompletableFuture[] cfs = futures.toArray(new CompletableFuture[futures.size()]);

    return CompletableFuture.allOf(cfs)
            .thenApply(ignored -> futures.stream()
                                    .map(CompletableFuture::join)
                                    .collect(Collectors.toList())
            );
}

3
Merhaba @Andrejs, bu kod snippet'inin ne yaptığını açıklar mısınız? Bunun birden fazla yerde önerildiğini görüyorum ama gerçekte ne olduğu konusunda kafam karıştı. İş parçacıklarından biri başarısız olursa istisnalar nasıl ele alınır?
VSEWHGHP

2
@VSEWHGHP Javadoc'tan: Verilen CompletableFutures'tan herhangi biri istisnai olarak tamamlanırsa, döndürülen CompletableFuture de bunu yapar ve bir CompletionException bu istisnayı nedeni olarak tutar.
Andrejs

1
Bu yüzden bunu takip ediyordum, bu snippet'i kullanmanın ancak başarıyla tamamlanan diğer tüm iş parçacıklarının değerlerini almanın herhangi bir yolu var mı? Sadece CompletableFutures listesi üzerinde yineleme ve CompletableFuture <Liste <T>> görmezden olsun çağrı dizi işlevi tüm iş parçacığı sonuç veya istisna ile tam olmasını sağlamak için özen gösteriyor?
VSEWHGHP

6
Bu farklı bir sorunu çözüyor. Eğer varsa Futureörneklerini bu yöntemi uygulamak mümkün değil. Bu dönüştürmek kolay değil Futureiçine CompletableFuture.
Jarekczek

bazı görevlerde istisnalarımız varsa çalışmaz.
slisnychyi

21

Bir kullan CompletableFutureJava 8

    // Kick of multiple, asynchronous lookups
    CompletableFuture<User> page1 = gitHubLookupService.findUser("Test1");
    CompletableFuture<User> page2 = gitHubLookupService.findUser("Test2");
    CompletableFuture<User> page3 = gitHubLookupService.findUser("Test3");

    // Wait until they are all done
    CompletableFuture.allOf(page1,page2,page3).join();

    logger.info("--> " + page1.get());

1
Bu kabul edilen cevap olmalı. Ayrıca resmi Spring belgelerinin bir parçasıdır: spring.io/guides/gs/async-method
maaw

Beklendiği gibi çalışır.
Dimon

15

ExecutorCompletionService kullanabilirsiniz . Belgelerin tam kullanım durumunuz için bir örneği bile vardır:

Bunun yerine, görev kümesinin ilk null olmayan sonucunu kullanmak, istisnalarla karşılaşanları yok saymak ve ilki hazır olduğunda diğer tüm görevleri iptal etmek istediğinizi varsayalım:

void solve(Executor e, Collection<Callable<Result>> solvers) throws InterruptedException {
    CompletionService<Result> ecs = new ExecutorCompletionService<Result>(e);
    int n = solvers.size();
    List<Future<Result>> futures = new ArrayList<Future<Result>>(n);
    Result result = null;
    try {
        for (Callable<Result> s : solvers)
            futures.add(ecs.submit(s));
        for (int i = 0; i < n; ++i) {
            try {
                Result r = ecs.take().get();
                if (r != null) {
                    result = r;
                    break;
                }
            } catch (ExecutionException ignore) {
            }
        }
    } finally {
        for (Future<Result> f : futures)
            f.cancel(true);
    }

    if (result != null)
        use(result);
}

Burada dikkat edilmesi gereken önemli şey, ecs.take () yönteminin yalnızca ilk gönderilen görevi değil , ilk tamamlanan görevi alacağıdır . Bu nedenle, onları işlemi bitirme (veya bir istisna atma) sırasında almalısınız.


3

Java 8 kullanıyorsanız ve CompletableFutures'yi değiştirmek istemiyorsanız , List<Future<T>>kullanılan bir akış için sonuçları almak için bir araç yazdım . Anahtar, map(Future::get)fırlattıkça yasaklanmış olmanızdır .

public final class Futures
{

    private Futures()
    {}

    public static <E> Collector<Future<E>, Collection<E>, List<E>> present()
    {
        return new FutureCollector<>();
    }

    private static class FutureCollector<T> implements Collector<Future<T>, Collection<T>, List<T>>
    {
        private final List<Throwable> exceptions = new LinkedList<>();

        @Override
        public Supplier<Collection<T>> supplier()
        {
            return LinkedList::new;
        }

        @Override
        public BiConsumer<Collection<T>, Future<T>> accumulator()
        {
            return (r, f) -> {
                try
                {
                    r.add(f.get());
                }
                catch (InterruptedException e)
                {}
                catch (ExecutionException e)
                {
                    exceptions.add(e.getCause());
                }
            };
        }

        @Override
        public BinaryOperator<Collection<T>> combiner()
        {
            return (l1, l2) -> {
                l1.addAll(l2);
                return l1;
            };
        }

        @Override
        public Function<Collection<T>, List<T>> finisher()
        {
            return l -> {

                List<T> ret = new ArrayList<>(l);
                if (!exceptions.isEmpty())
                    throw new AggregateException(exceptions, ret);

                return ret;
            };

        }

        @Override
        public Set<java.util.stream.Collector.Characteristics> characteristics()
        {
            return java.util.Collections.emptySet();
        }
    }

Bu AggregateExceptionC # 's gibi çalışan bir gerekir

public class AggregateException extends RuntimeException
{
    /**
     *
     */
    private static final long serialVersionUID = -4477649337710077094L;

    private final List<Throwable> causes;
    private List<?> successfulElements;

    public AggregateException(List<Throwable> causes, List<?> l)
    {
        this.causes = causes;
        successfulElements = l;
    }

    public AggregateException(List<Throwable> causes)
    {
        this.causes = causes;
    }

    @Override
    public synchronized Throwable getCause()
    {
        return this;
    }

    public List<Throwable> getCauses()
    {
        return causes;
    }

    public List<?> getSuccessfulElements()
    {
        return successfulElements;
    }

    public void setSuccessfulElements(List<?> successfulElements)
    {
        this.successfulElements = successfulElements;
    }

}

Bu bileşen tam olarak C # 'ın Task.WaitAll işlevi görür . Ben CompletableFuture.allOf(ile eşdeğer Task.WhenAll) aynı yapan bir varyant üzerinde çalışıyorum

Bunu yapmamın nedeni, Spring'i kullanmam ListenableFutureve CompletableFuturedaha standart bir yol olmasına rağmen bağlantı kurmak istememem.


1
Eşdeğer bir AggregateException gereksinimini görmek için oy verin.
granadaCoder

Bu tesisi kullanmanın bir örneği iyi olurdu.
XDS

1

Bir CompletableFutures Listesini birleştirmek istiyorsanız, bunu yapabilirsiniz:

List<CompletableFuture<Void>> futures = new ArrayList<>();
// ... Add futures to this ArrayList of CompletableFutures

// CompletableFuture.allOf() method demand a variadic arguments
// You can use this syntax to pass a List instead
CompletableFuture<Void> allFutures = CompletableFuture.allOf(
            futures.toArray(new CompletableFuture[futures.size()]));

// Wait for all individual CompletableFuture to complete
// All individual CompletableFutures are executed in parallel
allFutures.get();

Future & CompletableFuture hakkında daha fazla bilgi için faydalı bağlantılar:
1. Future: https://www.baeldung.com/java-future
2. CompletableFuture: https://www.baeldung.com/java-completablefuture
3. CompletableFuture: https : //www.callicoder.com/java-8-completablefuture-tutorial/


0

belki bu yardımcı olacaktır (hiçbir şey ham iplik ile değiştirilir, evet!) Ben Futureayrı bir iplik ile her adam çalıştırmak öneririz (paralel gider), o zaman hiç biri hata var, sadece yönetici ( Handlersınıf) sinyal .

class Handler{
//...
private Thread thisThread;
private boolean failed=false;
private Thread[] trds;
public void waitFor(){
  thisThread=Thread.currentThread();
  List<Future<Object>> futures = getFutures();
  trds=new Thread[futures.size()];
  for (int i = 0; i < trds.length; i++) {
    RunTask rt=new RunTask(futures.get(i), this);
    trds[i]=new Thread(rt);
  }
  synchronized (this) {
    for(Thread tx:trds){
      tx.start();
    }  
  }
  for(Thread tx:trds){
    try {tx.join();
    } catch (InterruptedException e) {
      System.out.println("Job failed!");break;
    }
  }if(!failed){System.out.println("Job Done");}
}

private List<Future<Object>> getFutures() {
  return null;
}

public synchronized void cancelOther(){if(failed){return;}
  failed=true;
  for(Thread tx:trds){
    tx.stop();//Deprecated but works here like a boss
  }thisThread.interrupt();
}
//...
}
class RunTask implements Runnable{
private Future f;private Handler h;
public RunTask(Future f,Handler h){this.f=f;this.h=h;}
public void run(){
try{
f.get();//beware about state of working, the stop() method throws ThreadDeath Error at any thread state (unless it blocked by some operation)
}catch(Exception e){System.out.println("Error, stopping other guys...");h.cancelOther();}
catch(Throwable t){System.out.println("Oops, some other guy has stopped working...");}
}
}

Yukarıdaki kod hata (kontrol etmedi) söylemek zorundayım, ama umarım çözümü açıklayabilir. lütfen bir deneyin.


0
 /**
     * execute suppliers as future tasks then wait / join for getting results
     * @param functors a supplier(s) to execute
     * @return a list of results
     */
    private List getResultsInFuture(Supplier<?>... functors) {
        CompletableFuture[] futures = stream(functors)
                .map(CompletableFuture::supplyAsync)
                .collect(Collectors.toList())
                .toArray(new CompletableFuture[functors.length]);
        CompletableFuture.allOf(futures).join();
        return stream(futures).map(a-> {
            try {
                return a.get();
            } catch (InterruptedException | ExecutionException e) {
                //logger.error("an error occurred during runtime execution a function",e);
                return null;
            }
        }).collect(Collectors.toList());
    };

0

CompletionService Callables'ınızı .submit () yöntemiyle alır ve hesaplanan futures'ları .take () yöntemiyle alabilirsiniz.

Unutmamanız gereken bir şey, .shutdown () yöntemini çağırarak ExecutorService'i sonlandırmaktır. Ayrıca bu yöntemi yalnızca yürütücü hizmetine bir referans kaydettiğinizde çağırabilirsiniz, bu nedenle bir tanesini sakladığınızdan emin olun.

Örnek kod - Paralel olarak çalışılacak sabit sayıda iş öğesi için:

ExecutorService service = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());

CompletionService<YourCallableImplementor> completionService = 
new ExecutorCompletionService<YourCallableImplementor>(service);

ArrayList<Future<YourCallableImplementor>> futures = new ArrayList<Future<YourCallableImplementor>>();

for (String computeMe : elementsToCompute) {
    futures.add(completionService.submit(new YourCallableImplementor(computeMe)));
}
//now retrieve the futures after computation (auto wait for it)
int received = 0;

while(received < elementsToCompute.size()) {
 Future<YourCallableImplementor> resultFuture = completionService.take(); 
 YourCallableImplementor result = resultFuture.get();
 received ++;
}
//important: shutdown your ExecutorService
service.shutdown();

Örnek kod - Paralel olarak çalışılacak dinamik sayıda iş öğesi için:

public void runIt(){
    ExecutorService service = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
    CompletionService<CallableImplementor> completionService = new ExecutorCompletionService<CallableImplementor>(service);
    ArrayList<Future<CallableImplementor>> futures = new ArrayList<Future<CallableImplementor>>();

    //Initial workload is 8 threads
    for (int i = 0; i < 9; i++) {
        futures.add(completionService.submit(write.new CallableImplementor()));             
    }
    boolean finished = false;
    while (!finished) {
        try {
            Future<CallableImplementor> resultFuture;
            resultFuture = completionService.take();
            CallableImplementor result = resultFuture.get();
            finished = doSomethingWith(result.getResult());
            result.setResult(null);
            result = null;
            resultFuture = null;
            //After work package has been finished create new work package and add it to futures
            futures.add(completionService.submit(write.new CallableImplementor()));
        } catch (InterruptedException | ExecutionException e) {
            //handle interrupted and assert correct thread / work packet count              
        } 
    }

    //important: shutdown your ExecutorService
    service.shutdown();
}

public class CallableImplementor implements Callable{
    boolean result;

    @Override
    public CallableImplementor call() throws Exception {
        //business logic goes here
        return this;
    }

    public boolean getResult() {
        return result;
    }

    public void setResult(boolean result) {
        this.result = result;
    }
}

0

Bunları içeren bir yardımcı sınıf var:

@FunctionalInterface
public interface CheckedSupplier<X> {
  X get() throws Throwable;
}

public static <X> Supplier<X> uncheckedSupplier(final CheckedSupplier<X> supplier) {
    return () -> {
        try {
            return supplier.get();
        } catch (final Throwable checkedException) {
            throw new IllegalStateException(checkedException);
        }
    };
}

Bunu yaptıktan sonra, statik bir içe aktarma kullanarak, aşağıdaki gibi tüm futures'ları bekleyebilirsiniz:

futures.stream().forEach(future -> uncheckedSupplier(future::get).get());

ayrıca tüm sonuçlarını şu şekilde toplayabilirsiniz:

List<MyResultType> results = futures.stream()
    .map(future -> uncheckedSupplier(future::get).get())
    .collect(Collectors.toList());

Sadece eski yazımı tekrar ziyaret etmek ve başka bir kederin olduğunu fark etmek:

Ancak buradaki sorun, örneğin 4. gelecek bir istisna atarsa, ilk 3 geleceğin mevcut olmasını gereksiz yere bekleyeceğim.

Bu durumda, basit çözüm bunu paralel olarak yapmaktır:

futures.stream().parallel()
 .forEach(future -> uncheckedSupplier(future::get).get());

Bu şekilde, ilk istisna, geleceği durdurmayacak olsa da, seri örneğinde olduğu gibi forEach-ifadesini kıracaktır, ancak hepsi paralel olarak beklediğinden, ilk 3'ün tamamlanmasını beklemek zorunda kalmayacaksınız.


0
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class Stack2 {   
    public static void waitFor(List<Future<?>> futures) {
        List<Future<?>> futureCopies = new ArrayList<Future<?>>(futures);//contains features for which status has not been completed
        while (!futureCopies.isEmpty()) {//worst case :all task worked without exception, then this method should wait for all tasks
            Iterator<Future<?>> futureCopiesIterator = futureCopies.iterator();
            while (futureCopiesIterator.hasNext()) {
                Future<?> future = futureCopiesIterator.next();
                if (future.isDone()) {//already done
                    futureCopiesIterator.remove();
                    try {
                        future.get();// no longer waiting
                    } catch (InterruptedException e) {
                        //ignore
                        //only happen when current Thread interrupted
                    } catch (ExecutionException e) {
                        Throwable throwable = e.getCause();// real cause of exception
                        futureCopies.forEach(f -> f.cancel(true));//cancel other tasks that not completed
                        return;
                    }
                }
            }
        }
    }
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(3);

        Runnable runnable1 = new Runnable (){
            public void run(){
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                }
            }
        };
        Runnable runnable2 = new Runnable (){
            public void run(){
                try {
                    Thread.sleep(4000);
                } catch (InterruptedException e) {
                }
            }
        };


        Runnable fail = new Runnable (){
            public void run(){
                try {
                    Thread.sleep(1000);
                    throw new RuntimeException("bla bla bla");
                } catch (InterruptedException e) {
                }
            }
        };

        List<Future<?>> futures = Stream.of(runnable1,fail,runnable2)
                .map(executorService::submit)
                .collect(Collectors.toList());

        double start = System.nanoTime();
        waitFor(futures);
        double end = (System.nanoTime()-start)/1e9;
        System.out.println(end +" seconds");

    }
}
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.