简体   繁体   English

如何从多个线程获取第一个结果并取消剩余

[英]How to get the first result from multiple threads and cancel remaining

I have request and multiple threads , which are looking for result in different ways and each thread should get some result at some point.. I need to take the result from the first finished thread , return this result and kill all of the remaining threads . 我有请求和多个线程 ,它们以不同的方式查找结果 ,每个线程应该在某个时候得到一些结果。我需要从第一个完成的线程获取结果 ,返回此结果并终止所有剩余的线程 I also have timeout when I'll return some default result.. 当我返回一些默认结果时,我也有超时..

I can think of two solutions, none of which seems "correct" to me.. 我能想到两个解决方案,对我来说似乎都没有“正确”。

1) Loop through the tasks, ask if they are finished, sleep for a while and return the first finished task it founds.. 1)循环完成任务,询问它们是否完成,睡眠一会儿并返回它找到的第一个完成的任务。

import java.util.ArrayList;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import org.apache.log4j.LogManager;

public class App {    
    public static final ExecutorService executors = Executors.newCachedThreadPool();

    public static void main(String[] args) {
        ArrayList<Future<String>> taskList = new ArrayList<>();
        taskList.add(executors.submit(new Task1()));
        taskList.add(executors.submit(new Task2()));

        String result = null;
        long start = System.currentTimeMillis();
        long timeout = 1000;

        while ((start + timeout) < System.currentTimeMillis()) {
            try {
                for (Future<String> future : taskList) {
                    if (future.isDone()) {
                        result = future.get();
                        break;
                    }
                }

                if (result != null) {
                    break;
                }

                Thread.sleep(10);
            } catch (InterruptedException | ExecutionException e) {
                e.printStackTrace();
            }
        }

        if (result == null) {
            result = "default..";
        }
    }

}

class Task1 implements Callable<String> {
    @Override
    public String call() throws Exception {
        // find result in one way
        return new String("result..");
    }
}

class Task2 implements Callable<String> {
    @Override
    public String call() throws Exception {
        // find result in some other way
        return new String("result..");
    }
}

2) Monitor each task with another thread by calling future.get(timeout, TimeUnit.MILLISECONDS); 2)通过调用future.get(timeout, TimeUnit.MILLISECONDS);监视每个任务与另一个线程future.get(timeout, TimeUnit.MILLISECONDS); and the first finished thread would then call future.cancel(true); 然后第一个完成的线程将调用future.cancel(true); for all other threads... 对于所有其他线程......

The first solution seems to me like wasting processor time, and the second seems to me like wasting threads.. 在我看来,第一个解决方案就像浪费处理器时间,而第二个解决方案在我看来就像浪费线程一样。

Finally, the Question is: Is there any better solution? 最后, 问题是:有没有更好的解决方案?

Thank you in advance 先感谢您

Edit: Thank you all for answers, I have solved this using "John H" 's answer: 编辑:谢谢大家的回答,我用“John H”的答案解决了这个问题:

There is a built in function that does this: https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/ExecutorService.html#invokeAny%28java.util.Collection,%20long,%20java.util.concurrent.TimeUnit%29 有一个内置函数可以做到这一点: https//docs.oracle.com/javase/7/docs/api/java/util/concurrent/ExecutorService.html#invokeAny%28java.util.Collection,%20long,% 20java.util.concurrent.TimeUnit 29%

This will invoke all the tasks you give it, and wait for the first one to return an answer up to a time limit. 这将调用您提供的所有任务,并等待第一个任务返回一个时间限制的答案。 If none of them return a result in time you can catch the TimeoutException and return the default value. 如果它们都没有及时返回结果,则可以捕获TimeoutException并返回默认值。 Otherwise you can use the first value it returns and it will take care of cancelling the rest of the tasks. 否则,您可以使用它返回的第一个值,它将负责取消其余任务。

How about a shared CountDownLatch ? 共享CountDownLatch怎么样?

Your main thread will wait at cdl.await(30, TimeUnit.SECONDS); 你的主线程将在cdl.await(30, TimeUnit.SECONDS);等待cdl.await(30, TimeUnit.SECONDS); and the worker threads will call cdl.countDown(); 而worker线程将调用cdl.countDown(); when they're done. 当他们完成了。

Another option is to use a shared Exchanger , which would allow you to retrieve the result easily. 另一种选择是使用共享的Exchanger ,这将允许您轻松地检索结果。 Although there might be a small chance that with exchanger two worker threads would exchange the result between them, but a simple workaround would be to check that a worker thread retrieves "dummyString", therefore knowing that it was the main thread that got the result. 尽管使用交换器两个工作线程可能会在它们之间交换结果的可能性很小,但一个简单的解决方法是检查工作线程是否检索“dummyString”,因此知道它是获得结果的主线程。

Main thread: 主线程:

myExchanger.exchange("dummyString", 30, TimeUnit.SECONDS); .

Worker threads: 工人线程:

while(!myExchanger.exchange(result).equals("dummyString"));

There is a built in function that does this: https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/ExecutorService.html#invokeAny%28java.util.Collection,%20long,%20java.util.concurrent.TimeUnit%29 有一个内置函数可以做到这一点: https//docs.oracle.com/javase/7/docs/api/java/util/concurrent/ExecutorService.html#invokeAny%28java.util.Collection,%20long,% 20java.util.concurrent.TimeUnit 29%

This will invoke all the tasks you give it, and wait for the first one to return an answer up to a time limit. 这将调用您提供的所有任务,并等待第一个任务返回一个时间限制的答案。 If none of them return a result in time you can catch the TimeoutException and return the default value. 如果它们都没有及时返回结果,则可以捕获TimeoutException并返回默认值。 Otherwise you can use the first value it returns and it will take care of cancelling the rest of the tasks. 否则,您可以使用它返回的第一个值,它将负责取消其余任务。

You could consider a SynchronousQueue . 你可以考虑一个SynchronousQueue

Main thread start all of the threads, giving each a reference to the queue, then just does a take on the queue (which will block until a worker thread does a put ). 主线程启动所有的线程,每个给人以队列的引用,然后就做了take队列(这将阻止,直到一个工作线程做一个put )。 First thread that discovers a result posts to the queue, releasing the main thread. 发现结果的第一个线程发布到队列,释放主线程。

Main thread then iterates over all workers cancelling them. 然后主线程迭代所有工人取消它们。

A default can be achieved with a thread that just waits for the timeout and put s the default value. 使用等待超时的线程put s设置为默认值即可实现默认值。

This would also work with Runnable s. 这也适用于Runnable

Sample code - seems to work but cancel does not. 示例代码 - 似乎工作,但cancel不。

public static void main(String[] args) {
  try {
    ArrayList<Future<String>> taskList = new ArrayList<>();
    BlockingQueue q = new SynchronousQueue();
    taskList.add(executors.submit(new Task1(q)));
    taskList.add(executors.submit(new Task2(q)));

    Object took = q.take();
    for (Future<String> task : taskList) {
      if (!task.isDone()) {
        task.cancel(true);
      }
    }
    System.out.println("Got " + took);
  } catch (InterruptedException ex) {
    Logger.getLogger(Test.class.getName()).log(Level.SEVERE, null, ex);
  }
}

It seems cancel is insufficiient. 似乎cancel是不充分的。 See here for alternatives. 请看这里的替代品。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM