미래의 목록을 기다리는 중


145

List선물 을 돌려주는 방법이 있습니다

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

이제 모든 미래가 성공적으로 처리되거나 미래에 의해 출력이 반환되는 작업이 예외를 throw 할 때까지 기다립니다. 하나의 과제가 예외를 던지더라도 다른 미래를 기다릴 필요는 없습니다.

간단한 접근 방식은

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

그러나 여기서 문제는 예를 들어 4 번째 미래에 예외가 발생하면 처음 3 개의 미래가 사용 가능할 때까지 불필요하게 기다릴 것입니다.

이것을 해결하는 방법? 카운트 다운이 어떤 식 으로든 도움이 되나요? isDoneJava 문서가 말하기 때문에 Future를 사용할 수 없습니다.

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
누가 그 미래를 만들어 내는가? 그들은 어떤 유형입니까? 인터페이스 java.util.concurrent.Future는 원하는 기능을 제공하지 않으며, 유일한 방법은 콜백과 함께 자신의 선물을 사용하는 것입니다.
Alexei Kaigorodov

ExecutionService모든 "일괄 처리"작업 에 대한 인스턴스를 생성하여 제출 한 다음 즉시 서비스를 종료하고 사용 awaitTermination()한다고 가정 할 수 있습니다.
millimoose

당신은을 사용할 수 있습니다 CountDownLatch당신은 귀하의 모든 선물의 몸을 감싸 경우 try..finally반드시 래치가 아니라 감소됩니다 확인하십시오.
millimoose

docs.oracle.com/javase/7/docs/api/java/util/concurrent/… 는 정확히 필요한 것을 수행합니다.
assylias

나는 execureservice에 작업을 제출할 때 YES @AlexeiKaigorodov, 내 미래는 타입 java.util.concurrent.I의이다는 callable.I GET Futture와 미래를 고소하고
user93796

답변:


124

CompletionService 를 사용 하여 선물이 준비 되 자마자 선물을 받고 예외 중 하나가 예외를 처리하면 처리를 취소 할 수 있습니다. 이 같은:

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

여전히 실행중인 작업 중 하나에 오류가 발생하면 취소하도록 추가 개선 할 수 있다고 생각합니다.


1
: 코드에 내가 언급 한 것과 동일한 문제가 있습니다. 앞으로 예외가 발생하면 코드는 여전히 미래 1,2,3이 완료되기를 기다립니다. 또는 completionSerice.take)가 먼저 완료되는 미래를 반환합니까?
user93796

1
타임 아웃은 어떻습니까? 완료 서비스에 최대 X 초 동안 기다리도록 할 수 있습니까?
user93796

1
없어야합니다. 선물에 대해서는 반복하지 않지만, 준비가 되 자마자 예외가 발생하지 않으면 처리 / 확인됩니다.
dcernahoschi

2
큐에 미래가 표시되기를 기다리는 시간 종료를 위해에 poll (seconds) 메소드가 있습니다 CompletionService.
dcernahoschi

github의 실제 예제는 다음과 같습니다 : github.com/princegoyal1987/FutureDemo
user18853

107

Java 8 을 사용하는 경우 제공된 모든 CompletableFuture가 완료된 후에 만 ​​콜백을 적용하는 CompletableFuture 및 CompletableFuture.allOf를 사용하여이 작업을 쉽게 수행 할 수 있습니다 .

// 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
안녕하세요 @Andrejs,이 코드 스 니펫이 무엇을하는지 설명해 주시겠습니까? 나는 이것이 여러 곳에서 제안 된 것을 보았지만 실제로 일어나는 일에 대해서는 혼란 스럽다. 스레드 중 하나가 실패하면 예외는 어떻게 처리됩니까?
VSEWHGHP

2
@VSEWHGHP javadoc에서 : 주어진 CompletableFuture가 예외적으로 완료되면 CompletionException도이 예외를 원인으로하여 CompleableException이 리턴됩니다.
Andrejs

1
그렇기 때문에이 스 니펫을 사용하는 방법이 있지만 성공적으로 완료 된 다른 모든 스레드의 값을 얻을 수 있습니까? CompletableFutures 목록을 반복하고 호출하면 CompletableFuture <List <T >>를 무시하고 호출하면 시퀀스 함수가 ​​모든 스레드가 결과 또는 예외로 완료되었는지 확인하므로 처리해야합니까?
VSEWHGHP

6
이것은 다른 문제를 해결하고 있습니다. Future인스턴스 가있는 경우이 방법을 적용 할 수 없습니다. 그것은 변환 할 쉬운 일이 아닙니다 Future으로 CompletableFuture.
Jarekczek

일부 작업에서 예외가 있으면 작동하지 않습니다.
slisnychyi

21

를 사용하여 CompletableFuture자바 (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
이것이 정답입니다. 또한 스프링의 공식 문서 : spring.io/guides/gs/async-method
maaw

예상대로 작동합니다.
Dimon

15

ExecutorCompletionService 를 사용할 수 있습니다 . 문서에는 정확한 사용 사례에 대한 예가 있습니다.

대신, 태스크 세트의 첫 번째 널이 아닌 결과를 사용하고 예외가 발생하는 것을 무시하고 첫 번째 태스크가 준비되면 다른 모든 태스크를 취소한다고 가정하십시오.

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

여기서 주목해야 할 것은 ecs.take ()는 처음 제출 된 작업뿐만 아니라 첫 번째로 완료된 작업을 가져옵니다 . 따라서 실행을 마치거나 예외를 던지는 순서대로 가져와야합니다.


3

Java 8을 사용하고 있고 CompletableFutures 를 조작하지 않으려면 List<Future<T>>스트리밍 사용 결과를 검색하는 도구를 작성했습니다 . 열쇠는 map(Future::get)던질 때 금지되어 있다는 것 입니다.

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

이것은 AggregateExceptionC #처럼 작동 하는 것이 필요합니다.

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

}

이 구성 요소는 C #의 Task.WaitAll 과 동일하게 작동합니다 . 나는 CompletableFuture.allOf(와 동등한 Task.WhenAll) 변형을 연구하고 있습니다.

내가 이것을 한 이유는 내가 스프링을 사용 하고 있고 더 표준적인 방법 임에도 불구하고 ListenableFuture포팅하고 싶지 않기 CompletableFuture때문입니다.


1
동등한 AggregateException이 필요한지 확인하십시오.
granadaCoder

이 기능을 사용하는 예가 좋습니다.
XDS

1

CompletableFutures 목록을 결합하려는 경우 다음을 수행 할 수 있습니다.

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에 대한 자세한 내용, 유용한 링크 :
1. 미래 : 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

어쩌면 이것은 도움이 될 것입니다 (아무것도 원시 스레드로 대체되지 않습니다. 그렇습니다!) 각 Future스레드를 분리 된 스레드로 실행하는 것이 좋습니다 (병렬로 진행됨). 오류가 발생하면 관리자에게 신호를 보냅니다 ( Handler클래스).

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

위의 코드가 오류가 발생했다고 말해야하지만 (확인하지 않았 음) 해결책을 설명 할 수 있기를 바랍니다. 시도하십시오.


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는 .submit () 메소드를 사용하여 Callables를 가져오고 .take () 메소드를 사용하여 계산 된 선물을 검색 할 수 있습니다.

잊지 말아야 할 것은 .shutdown () 메소드를 호출하여 ExecutorService를 종료하는 것입니다. 또한 실행기 서비스에 대한 참조를 저장 한 경우에만이 메소드를 호출 할 수 있으므로이를 유지하십시오.

예제 코드-고정 된 수의 작업 항목을 병렬로 처리하려면 다음을 수행하십시오.

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

예제 코드-동적으로 많은 수의 작업 항목을 동시에 처리하려면 다음을 수행하십시오.

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

다음을 포함하는 유틸리티 클래스가 있습니다.

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

정적 가져 오기를 사용하면 다음과 같은 모든 미래를 간단하게 기다릴 수 있습니다.

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

다음과 같이 모든 결과를 수집 할 수도 있습니다.

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

내 이전 게시물을 다시 방문하고 또 다른 슬픔이 있음을 알았습니다.

그러나 여기서 문제는 예를 들어 4 번째 미래에 예외가 발생하면 처음 3 개의 미래가 사용 가능할 때까지 불필요하게 기다릴 것입니다.

이 경우 간단한 해결책은 이것을 병렬로 수행하는 것입니다.

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

이런 식으로 첫 번째 예외는 미래를 멈추지 않지만 직렬 예제에서와 같이 forEach-statement를 중단하지만 모두 병렬로 대기하기 때문에 첫 번째 3이 완료 될 때까지 기다릴 필요가 없습니다.


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

    }
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.