问题
Problem: I have a requirement to call a dao method at fix rate say every 10 sec, then I need to check if the result is valid if yes exit, else keep on calling that method every 10 sec till I get a valid result or defined time out (say 2 min) is over.
Approaches: I want to keep the task and scheduler logic separate, and write a task in such a way that it can be used by different classes having similar requirement.
One way I can think is to define a new poller task
public abstract class PollerTask<T> implements Runnable {
abstract public boolean isValid(T result);
abstract public T task();
private T result;
private volatile boolean complete;
public boolean isComplete() {
return complete;
}
public T getResult() {
return result;
}
@Override
final public void run() {
result = task();
if (complete = isValid(result)) {
//may be stop scheduler ??
}
}
}
User need to simply provide implementation of task and isValid;
Then we can define a separate class that takes pooling freq and timeout and creates a scheduled executor and submit this task
public class PollerTaskExecutor {
private int pollingFreq;
private int timeout;
private ScheduledExecutorService executor;
private ScheduledExecutorService terminator;
private ExecutorService condition;
private volatile boolean done;
private ScheduledFuture future;
public PollerTaskExecutor(int pollingFreq, int timeout) {
this.pollingFreq = pollingFreq;
this.timeout = timeout;
executor = Executors.newSingleThreadScheduledExecutor();
terminator = Executors.newSingleThreadScheduledExecutor();
condition = Executors.newSingleThreadExecutor();
}
public void submitTaskForPolling(final PollerTask pollerTask) {
future = executor.scheduleAtFixedRate(pollerTask, 0, pollingFreq, TimeUnit.SECONDS);
terminator.schedule(new Runnable() {
@Override
public void run() {
complete();
}
}, timeout, TimeUnit.SECONDS);
condition.execute(new Runnable() {
@Override
public void run() {
if (pollerTask.isComplete()) {
complete();
}
}
});
}
public boolean isDone() {
return done;
}
public void complete() {
future.cancel(false);
executor.shutdown();
terminator.shutdown();
condition.shutdown();
done = true;
}
now user can wait till pollerExecutor.isDone returns true and get the result. I had to use three executors for following purposes:
- executor to run task at fix interval
- executor to stop all when time out is over
- executor to stop all if valid result is obtained before time out.
Can someone please suggest a better approach, this seems to be complicated for such a trivial task ?
回答1:
Make it a self-scheduling task. In pseudo code:
public class PollingTaskRunner {
...
CountDownLatch doneWait = new CountDownLatch(1);
volatile boolean done;
PollingTaskRunner(Runnable pollingTask, int frequency, int period) {
...
endTime = now + period;
executor.schedule(this, 0);
}
run() {
try {
pollingTask.run();
} catch (Exception e) {
...
}
if (pollingTask.isComplete() || now + frequency > endTime) {
done = true;
doneWait.countDown();
executor.shutdown();
} else {
executor.schedule(this, frequency);
}
}
await() {
doneWait.await();
}
isDone() {
return done;
}
}
It is not that complicated but add plenty of debug statements the first time you run/test this so you know what is going on. Once it is running as intended, it is easy to re-use the pattern.
回答2:
A slightly simpler method, you don't need a separate executor service for the terminator, you could simply push the terminator task into the same executor.
Even simpler. Have PollerTask
place it's result in a BlockingQueue
. Then have the PollingTaskRunner
do a timed poll
on that BlockingQueue
. Whenever control is returned from the poll
call ScheduledFuture.cancel
because the task either succeeded or timed out.
来源:https://stackoverflow.com/questions/23226563/scheduled-executor-poll-for-result-at-fix-rate-and-exit-if-timeout-or-result-va