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