如何在Java中对计算进行时间限制并能够获得到目前为止计算的所有结果,即使在时间预算结束(超时)时也是如此?

在我正在开发的框架中,用户可以选择在后台运行某个耗时的任务,同时做其他事情.该任务计算一系列结果.在某些时候,当他/她需要后台任务的结果时,可以等待一段时间,直到:

> a)发生超时(在这种情况下,用户希望获得到目前为止计算的所有结果,如果存在的话);要么
> b)达到最大数量或计算结果(正常结束),

以先发生者为准.

问题是:即使发生超时,用户仍然希望到目前为止计算结果.

我尝试使用Future< V> .get(long timeout,TimeUnit unit)和Callable< V>这样做了. -derived类,但是当发生TimeoutException时,它通常意味着任务过早完成,因此没有可用的结果.因此我不得不添加一个getPartialResults()方法(参见下面的DiscoveryTask),我担心这种用法对潜在用户来说太违反直觉了.

发现调用:

public Set<ResourceId> discover(Integer max, long timeout, TimeUnit unit)
    throws DiscoveryException
{
    DiscoveryTask task = new DiscoveryTask(max);
    Future<Set<ResourceId>> future = taskExec.submit(task);
    doSomethingElse();
    try {
        return future.get(timeout, unit);
    } catch (CancellationException e) {
        LOG.debug("Discovery cancelled.", e);
    } catch (ExecutionException e) {
        throw new DiscoveryException("Discovery failed to execute.", e);
    } catch (InterruptedException e) {
        LOG.debug("Discovery interrupted.", e);
    } catch (TimeoutException e) {
        LOG.debug("Discovery time-out.");
    } catch (Exception e) {
        throw new DiscoveryException("Discovery failed unexpectedly.", e);
    } finally {
        // Harmless if task already completed
        future.cancel(true); // interrupt if running
    }
    return task.getPartialResults(); // Give me what you have so far!
}

发现实现:

public class DiscoveryTask extends Callable<Set<ResourceId>> 
        implements DiscoveryListener
{
    private final DiscoveryService discoveryService;
    private final Set<ResourceId> results;
    private final CountDownLatch doneSignal;
    private final MaximumLimit counter;
    //...
    public DiscoveryTask(Integer maximum) {
        this.discoveryService = ...;
        this.results = Collections.synchronizedSet(new HashSet<ResourceId>());
        this.doneSignal = new CountDownLatch(1);
        this.counter = new MaximumLimit(maximum);
        //...
    }

    /**
     * Gets the partial results even if the task was canceled or timed-out.
     * 
     * @return The results discovered until now.
     */
    public Set<ResourceId> getPartialResults() {
        Set<ResourceId> partialResults = new HashSet<ResourceId>();
        synchronized (results) {
            partialResults.addAll(results);
        }
        return Collections.unmodifiableSet(partialResults);
    }

    public Set<ResourceId> call() throws Exception {
        try {
            discoveryService.addDiscoveryListener(this);
            discoveryService.getRemoteResources();
            // Wait...
            doneSignal.await();
        } catch (InterruptedException consumed) {
            LOG.debug("Discovery was interrupted.");
        } catch (Exception e) {
            throw new Exception(e);
        } finally {
            discoveryService.removeDiscoveryListener(this);
        }
        LOG.debug("Discovered {} resource(s).", results.size());
        return Collections.unmodifiableSet(results);
    }

    // DiscoveryListener interface
    @Override
    public void discoveryEvent(DiscoveryEvent de) {
        if (counter.wasLimitReached()) {
            LOG.debug("Ignored discovery event {}. "
                + "Maximum limit of wanted resources was reached.", de);
            return;
        }
        if (doneSignal.getCount() == 0) {
            LOG.debug("Ignored discovery event {}. "
                + "Discovery of resources was interrupted.", de);
            return;
        }
        addToResults(de.getResourceId());
    }

    private void addToResults(ResourceId id) {
        if (counter.incrementUntilLimitReached()) {
            results.add(id);
        } else {
            LOG.debug("Ignored resource {}. Maximum limit reached.",id);
            doneSignal.countDown();
        }
    }
}

在Brian Goetz等人的Java Concurrency in Practice一书的第6章中,作者展示了相关问题的解决方案,但在这种情况下,所有结果都可以并行计算,这不是我的情况.确切地说,我的结果取决于外部来源,因此我无法控制它们何时到来.我的用户在调用任务执行之前定义了他想要的最大结果数,以及她在准备好获得结果后同意等待的最长时间限制.

这对你好吗?你会以不同的方式做吗?有没有更好的方法?

最佳答案 将(较短的)超时传递给任务本身,并在达到此“软超时”时提前返回.然后结果类型可以有一个标志,告诉结果是否完美:

Future<Result> future = exec.submit(new Task(timeout*.9));
//if you get no result here then the task misbehaved, 
//i.e didn't obey the soft timeout. 
//This should be treated as a bug
Result result = future.get(timeout);
if (result.completedInTime()) {
    doSomethingWith(result.getData());        
} else {
    doSomethingElseWith(result.getData());       
}
点赞