等待未知数量的异步任务

时间:2012-07-25 17:14:19

标签: java multithreading

我有一个任务队列,以及一个在几秒钟内查看队列一次的线程,如果有任务则执行它。

我有另一个代码部分(当然是在另一个线程中),它在循环中创建任务(我无法从循环外部预先知道任务的数量)并将它们插入队列。任务包含一些“结果”对象,外部线程(创建这些任务)需要等待所有任务完成,最后从每个任务中获取结果。 问题是我无法将java Semaphore \ CountDownLatch等传递给结果对象,因为我事先并不知道监视器的数量。 我也不能使用使用invokeAll的Executor或等待Future对象,因为任务是unynchrnized(外部线程只是将任务发送到队列而另一个线程将在他有时间执行任务时)。

我唯一想到的解决方案就是创建一些“倒置信号量”类,它包含一组结果和一个监视器计数器。 getResult函数将检查计数器是否== 0,如果答案是肯定的,则会通知某个锁定对象,并且getResult函数将等待此锁定:

public class InvertedSemaphore<T> {
    Set<T> resultSet;
    int usages;
    final Object c;

    public InvertedSemaphore() {
        resultSet = Collections.synchronizedSet(new HashSet<T>());
        usages = 0;
        c = new Object();
    }

    public void addResult(T result) {
        resultSet.add(result);
    }

    public void addResults(Set<T> result) {
        resultSet.addAll(result);
    }


    public void acquire() {
        usages++;
    }

    public void release() {
        synchronized (c) {
            if (--usages == 0) {
                c.notify();
            }
        }
    }

    public Set<T> getResults() {
        synchronized (c) {
            try {
                while (usages > 0) {
                    c.wait();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        return resultSet;
    }

}

每个addTask方法都会调用semaphore.acquire,每个(未同步的)任务都会在任务结束时调用semaphore.release。

听起来相当复杂,我很确定在java并发库中有更好的解决方案。

任何想法都会受到关注:)

3 个答案:

答案 0 :(得分:3)

如果不需要按顺序处理任务,请使用ExecutorCompletionService

更一般地说,没有必要在invokeAll上使用ExecutorService来获得结果的FutureExecutorService#submit可以用于此目的,或者可选地,正在创建的任务本身可以实现Future,从而允许任务的创建者在稍后的时间点请求结果。

一些代码:

class MyTask {
    AtomicReference<?> result = new AtomicReference<?>();

    void run() {
       //do stuff here
       result.set(/* the result of the calculation */);
    }

    boolean resultReady() {
        return result.get()!=null;
    }

    ? get() {
        return result.get();
    }
}

......代码中的其他地方

void createTasks() {
    Collection<MyTask> c = new ...;

    while(indeterminable condition) {
        MyTask task = new MyTask();
        c.add(task);
        mysteryQueue.add(task);
    }

    while(haven't received all results) {
        MyTask task = c.get(...); //or iterate or whatever
        ? result = task.get();
        if (result!=null) {
            //do stuff, probably remove the task from the collection c would be smart
        }
    }
}

答案 1 :(得分:1)

一个想法是为结果使用单独的队列 因此,您将拥有一个阻塞队列,线程A为线程B放置任务,从而具有生产者 - 消费者方法,并且当每个任务完成时,结果可以放在 second < / em>结果队列反转消费者 - 生产者角色,因为现在最初创建任务的线程A将使用第二个队列的结果。

答案 2 :(得分:1)

您可以执行以下操作: 每个生产者都将拥有自己的队列。生产者将传递一种方法来向此队列报告任务本身。当任务完成运行时,它会将其结果排入此队列。这是一些代码描述的野兽:

class Result{}

interface IResultCallback{
    void resultReady(Result r); // this is an abstraction of the queue
}

class Producer implements IResultCallback{ 
    // the producer needs to pass itself to the constructor of the task,
    // the task will only see its "resultReady" facade and will be able to report to it. 
    // the producer can aggragte the results at it will and execute its own computation as 
        // as soon it is ready

    Queue<Result> results; // = init queue

    @Override
    public void resultReady(Result r) {
        results.add(r);

        if(results.size() == 9){
            operate();
        }
        results.clear();
    }

    public void operate(){
        // bla bla
    }
}

public class Task {
    IResultCallback callback;

    public Task(IResultCallback callback){
        this.callback = callback;
    }
    public void execute(){
        // bla bla

        Result r = null; // init result;
        callback.resultReady(r);
    }
}