How to connect time with calculation in Java and get all the results calculated so far, even when the time budget ends (timeout)?

As part of the development that I am developing, the user can choose to perform a specific time task in the background while doing something else. This task computes a series of results. At some point, when he needs the results of a background task, it is acceptable to wait some more time until:

  • a) a timeout occurs (in this case, the user would like to get all the results calculated so far, if they exist); or

  • b) the maximum number or calculated results are achieved (normal completion),

whichever comes first.

Launched: Even if a timeout occurs, the user still needs the results calculated so far.

I tried to do this using the Future<V>.get(long timeout, TimeUnit unit) and Callable<V> -derived class, but it happens that when a Timeout exception occurs, it usually means that the task was prematurely completed, so the results are not available . Thus, I had to add the getPartialResults() method (see DiscoveryTask below), and I am afraid that this use is too inconsistent for potential users.

Discovery call:

 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! } 

Detection Implementation:

 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(); } } } 

In chapter 6 of the Java Concurrency book in practice by Brian Goetz et al., The authors show a solution to a related problem, but in this case all the results can be calculated in parallel, which is not my case. To be precise, my results depend on external sources, so I don’t I can control when they come. My user determines the desired maximum number of results that he wants before calling the task, and the maximum time limit that she agreed to wait after she is ready to receive the results.

This is normal? Would you do it differently? Is there a better approach?

+4
source share
3 answers

Skip the (shorter) timeout of the task itself and return it prematurely when it reaches this “soft timeout”. Then the result type may have a flag indicating whether the result is perfect or not:

 Future<Result> future = exec.submit(new Task(timeout*.9)); //if you get no result here then the task misbehaved, //ie 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()); } 
+2
source

If an individual result comes cheap, then do your routine to generate results and add Them to the output queue and see how much time is left and compare it with the time when the current results took. Only calculate the following result if you can do it on time.

This will allow you to stay single-threaded, which usually makes your code simpler and therefore less error prone.

0
source

Something like that:

 public static class Consumer<T> { private BlockingQueue<T> queue; private T lastElement; public Consumer(BlockingQueue<T> queue, T lastElement) { this.queue = queue; this.lastElement = lastElement; } public Collection<T> acquireResults(long timeout, TimeUnit timeoutTimeUnit) throws InterruptedException { LinkedList<T> result = new LinkedList<T>(); queue.drainTo(result); if (result.getLast() == lastElement) return result; else result.add(queue.poll(timeout, timeoutTimeUnit)); result.removeLast(); return result; } } public static void main(String[] args) throws InterruptedException { String lastElement = "_lastElement"; BlockingQueue<String> queue = new LinkedBlockingQueue<String>(); Consumer<String> consumer = new Consumer<String>(queue, lastElement); for (int i=0; i<100; i++) queue.put(UUID.randomUUID().toString()); System.out.println(consumer.acquireResults(5, TimeUnit.SECONDS)); queue.put("foo"); queue.put("bar"); queue.put(lastElement); System.out.println(consumer.acquireResults(5, TimeUnit.SECONDS)); } 

You will probably want to use a wrapper of results to indicate that some result is indeed the last result, not a magic value.

0
source

Source: https://habr.com/ru/post/1301357/


All Articles