Chờ đợi trong danh sách Tương lai


145

Tôi có một phương thức trả về một Listtương lai

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

Bây giờ tôi muốn đợi cho đến khi tất cả các hợp đồng tương lai được xử lý thành công hoặc bất kỳ nhiệm vụ nào có đầu ra được trả về bởi một tương lai sẽ ném ra một ngoại lệ. Ngay cả khi một nhiệm vụ ném ra một ngoại lệ, không có lý do gì để chờ đợi các tương lai khác.

Cách tiếp cận đơn giản sẽ là

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

Nhưng vấn đề ở đây là, ví dụ, nếu tương lai thứ 4 ném ra một ngoại lệ, thì tôi sẽ chờ đợi một cách không cần thiết cho 3 tương lai đầu tiên có sẵn.

Làm thế nào để giải quyết điều này? Sẽ đếm ngược giúp đỡ bằng cách nào? Tôi không thể sử dụng Tương lai isDonevì tài liệu java nói

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
Ai tạo ra những tương lai? Họ thuộc loại nào? Giao diện java.util.concản.Future không cung cấp chức năng bạn muốn, cách duy nhất là sử dụng Tương lai của riêng bạn với các cuộc gọi lại.
Alexei Kaigorodov

Bạn có thể tạo một ví dụ ExecutionServicecho mỗi "lô" nhiệm vụ, gửi chúng cho nó, sau đó tắt ngay dịch vụ và sử dụng awaitTermination()nó.
millimoose

Bạn có thể sử dụng một CountDownLatchnếu bạn bọc cơ thể của tất cả các tương lai của bạn trong một try..finallyđể đảm bảo rằng chốt cũng bị giảm.
millimoose

docs.oracle.com/javase/7/docs/api/java/util/concản/iêu làm chính xác những gì bạn cần.
assylias

@AlexeiKaigorodov CÓ, tương lai của tôi thuộc loại java.util.concản. Tôi đang kiện tương lai với khả năng gọi được. Tôi nhận được Futture khi tôi gửi một nhiệm vụ cho dịch vụ thực thi
user93796

Câu trả lời:


124

Bạn có thể sử dụng Dịch vụ Hoàn thành để nhận tương lai ngay khi chúng sẵn sàng và nếu một trong số chúng ném ngoại lệ, hãy hủy quá trình xử lý. Một cái gì đó như thế này:

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

Tôi nghĩ bạn có thể cải thiện hơn nữa để hủy bỏ mọi tác vụ vẫn đang thực thi nếu một trong số chúng gây ra lỗi.


1
: Mã của bạn có cùng một vấn đề mà tôi đã đề cập trong bài đăng của mình. Nếu trong tương lai ném ngoại lệ thì mã sẽ vẫn chờ 1,2,3 trong tương lai hoàn tất. hoặc sẽ hoàn thànhSerice.take) sẽ trả về tương lai hoàn thành đầu tiên?
dùng93796

1
Còn thời gian chờ thì sao? Tôi có thể bảo dịch vụ hoàn thành đợi tối đa X giây không?
user93796

1
Không nên có. Nó không lặp đi lặp lại trong tương lai, nhưng ngay khi một người sẵn sàng, nó được xử lý / xác minh nếu không ném ngoại lệ.
dcernahoschi

2
Để hết thời gian chờ đợi một tương lai xuất hiện trên hàng đợi, có một phương thức thăm dò ý kiến ​​(giây) trên CompletionService.
dcernahoschi

Dưới đây là ví dụ hoạt động trên github: github.com/princegoyalty1987/FutureDemo
user18853

107

Nếu bạn đang sử dụng Java 8 thì bạn có thể thực hiện việc này dễ dàng hơn với CompleteableFuture và CompleteableFuture.allOf , chỉ áp dụng gọi lại sau khi hoàn thành tất cả CompleteableFutures.

// 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
Xin chào @Andrejs, bạn có thể vui lòng giải thích đoạn mã này làm gì không. Tôi thấy điều này được đề xuất ở nhiều nơi nhưng bối rối không biết chuyện gì đang thực sự xảy ra. Làm thế nào các ngoại lệ được xử lý nếu một trong các chủ đề thất bại?
VSEWHGHP

2
@VSEWHGHP Từ javadoc: Nếu bất kỳ một trong số CompleteableFutures nào được đưa ra hoàn toàn ngoại lệ, thì CompleteableFuture được trả lại cũng làm như vậy, với CompleteionException giữ ngoại lệ này là nguyên nhân của nó.
Andrejs

1
Vì vậy, tôi đã theo dõi về điều đó, có cách nào để sử dụng đoạn mã này nhưng có được các giá trị cho tất cả các luồng khác đã hoàn thành thành công không? Tôi có nên lặp lại danh sách CompleteableFutures và gọi bỏ qua phần CompleteableFuture <Danh sách <T >> vì chức năng chuỗi đảm bảo đảm bảo tất cả các luồng hoàn thành dù có kết quả hay ngoại lệ?
VSEWHGHP

6
Đây là giải quyết một vấn đề khác nhau. Nếu bạn có Futuretrường hợp, bạn không thể áp dụng phương pháp này. Không dễ để chuyển đổi Futurethành CompletableFuture.
Jarekczek

nó sẽ không hoạt động nếu chúng ta có ngoại lệ trong một số nhiệm vụ.
slisnychyi

21

Sử dụng một CompletableFuturetrong Java 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
Đây phải là câu trả lời được chấp nhận. Ngoài ra, đây là một phần của tài liệu chính thức về mùa xuân: spring.io/guides/gs/async-method
maaw

Hoạt động như mong đợi.
Dimon

15

Bạn có thể sử dụng ExecutorCompletionService . Tài liệu thậm chí có một ví dụ cho trường hợp sử dụng chính xác của bạn:

Thay vào đó, giả sử rằng bạn muốn sử dụng kết quả không null đầu tiên của nhóm tác vụ, bỏ qua mọi trường hợp gặp ngoại lệ và hủy tất cả các tác vụ khác khi nhiệm vụ đầu tiên sẵn sàng:

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

Điều quan trọng cần lưu ý ở đây là ecs.take () sẽ nhận được nhiệm vụ hoàn thành đầu tiên , không chỉ là nhiệm vụ đầu tiên được gửi. Vì vậy, bạn nên có được chúng theo thứ tự hoàn thành việc thực hiện (hoặc ném một ngoại lệ).


3

Nếu bạn đang sử dụng Java 8 và không muốn thao tác CompletableFuture, tôi đã viết một công cụ để lấy kết quả cho List<Future<T>>việc sử dụng phát trực tuyến. Điều quan trọng là bạn bị cấm map(Future::get)khi nó ném.

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

Điều này cần một AggregateExceptionhoạt động như của C #

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

}

Thành phần này hoạt động chính xác như Nhiệm vụ của C # .Aa ALL . Tôi đang làm việc trên một biến thể giống như CompletableFuture.allOf(tương đương với Task.WhenAll)

Lý do tại sao tôi làm điều này là vì tôi đang sử dụng Spring ListenableFuturevà không muốn chuyển sang CompletableFuturemặc dù đó là một cách tiêu chuẩn hơn


1
Upvote khi thấy sự cần thiết của một AggregateException tương đương.
granadaCoder

Một ví dụ về việc sử dụng cơ sở này sẽ tốt đẹp.
XDS

1

Trong trường hợp bạn muốn kết hợp Danh sách Hoàn thành, bạn có thể làm điều này:

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

Để biết thêm chi tiết về Tương lai & Hoàn thànhFuture, các liên kết hữu ích:
1. Tương lai: https://www.baeldung.com/java-future
2. CompleteableFuture: https://www.baeldung.com/java-completablefuture
3. Hoàn thànhFuture: https : //www.callicoder.com/java-8-completablefuture-tutorial/


0

có lẽ đây sẽ giúp (sẽ không có gì thay thế bằng chủ đề thô, yeah!) Tôi đề nghị chạy mỗi Futurechàng trai với một chủ đề riêng biệt (họ đi song song), sau đó khi nào một trong những lỗi có, nó chỉ là dấu hiệu người quản lý ( Handlerlớp).

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

Tôi phải nói rằng đoạn mã trên sẽ bị lỗi (không kiểm tra), nhưng tôi hy vọng tôi có thể giải thích giải pháp. xin hãy thử


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

Dịch vụ CompleteionService sẽ sử dụng Callables của bạn bằng phương thức .submit () và bạn có thể truy xuất tương lai được tính toán bằng phương thức .take ().

Một điều bạn không được quên là chấm dứt dịch vụ ExecutorService bằng cách gọi phương thức .shutdown (). Ngoài ra, bạn chỉ có thể gọi phương thức này khi bạn đã lưu tham chiếu đến dịch vụ thực thi, vì vậy hãy đảm bảo giữ một tham chiếu.

Mã ví dụ - Đối với một số mục công việc cố định được xử lý song song:

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

Mã ví dụ - Để có một số lượng lớn các mục công việc được xử lý song song:

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

Tôi đã có một lớp tiện ích chứa những thứ này:

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

Khi bạn đã có điều đó, bằng cách sử dụng nhập tĩnh, bạn có thể chờ đợi tất cả các tương lai như thế này:

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

bạn cũng có thể thu thập tất cả các kết quả của họ như thế này:

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

Chỉ cần xem lại bài viết cũ của tôi và nhận thấy rằng bạn có một nỗi đau khác:

Nhưng vấn đề ở đây là, ví dụ, nếu tương lai thứ 4 ném ra một ngoại lệ, thì tôi sẽ chờ đợi một cách không cần thiết cho 3 tương lai đầu tiên có sẵn.

Trong trường hợp này, giải pháp đơn giản là làm điều này song song:

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

Bằng cách này, ngoại lệ đầu tiên, mặc dù nó sẽ không dừng lại trong tương lai, sẽ phá vỡ câu lệnh forEach, như trong ví dụ nối tiếp, nhưng vì tất cả chờ đợi song song, bạn sẽ không phải đợi 3 đầu tiên hoàn thành.


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

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