Dans un cadre que je développe, l'utilisateur peut choisir d'exécuter une certaine tâche chronophage en tâche de fond tout en faisant autre chose. Cette tâche calcule une série de résultats. En un moment donné, quand il/elle a besoin des résultats de la tâche de fond, il est acceptable d'attendre un peu plus de temps jusqu'à ce que:Comment faire un calcul temporel en Java et être capable de calculer tous les résultats jusqu'à maintenant, même quand le budget-temps se termine (time-out)?
a) une temporisation se produit (dans ce cas, l'utilisateur souhaite obtenir tous les résultats calculés jusqu'à présent, s'ils existent); ou
b) un nombre maximum ou les résultats calculés est atteint (fin normale),
selon la première éventualité. Le gotcha est: Même si un délai d'attente se produit, l'utilisateur veut toujours les résultats calculés jusqu'à présent.
J'ai essayé de le faire en utilisant Future<V>.get(long timeout, TimeUnit unit)
et une Callable<V>
classe dérivée de, mais il arrive que lorsqu'un TimeoutException se produit, cela signifie généralement la tâche a été prématurément terminé, donc pas de résultats sont disponibles. J'ai donc dû ajouter une méthode getPartialResults()
(voir DiscoveryTask
ci-dessous) et j'ai peur que cette utilisation soit trop contre-intuitive pour les utilisateurs potentiels.
invocation de découverte:
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!
}
réalisation de découverte:
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();
}
}
}
Dans le chapitre 6 du livre Java Concurrency dans la pratique de Brian Goetz et al, les auteurs montrent une solution pour une problème connexe, mais dans ce cas tous les résultats peuvent être calculés en parallèle, ce qui n'est pas mon cas. Pour être précis, mes résultats dépendent de sources externes, donc je n'ai aucun contrôle sur quand ils viennent. Mon utilisateur définit le nombre maximum de résultats souhaités avant d'invoquer l'exécution de la tâche, et une limite de temps maximum qu'elle a accepté d'attendre après qu'elle soit prête à obtenir les résultats.
C'est OK pour vous? Le feriez-vous différemment? Y a-t-il une meilleure approche?
Avez-vous besoin d'informations supplémentaires concernant la solution suggérée? – yawn