Остановите периодическую задачу изнутри самой задачи, выполняемой в ScheduledExecutorService
Есть ли хороший способ остановить повторение задачи изнутри самой задачи при запуске в ScheduledExecutorService?
Допустим, у меня есть следующая задача:
Future<?> f = scheduledExecutor.scheduleAtFixedRate(new Runnable() {
int count = 0;
public void run() {
System.out.println(count++);
if (count == 10) {
// ??? cancel self
}
}
}, 1, 1, TimeUnit.SECONDS);
Снаружи легко отменить через f.cancel(), но как я могу остановить повтор в указанном месте? (Передача Future через AtomicReference небезопасна, поскольку существует потенциальное окно, когда scheduleAtFixedRate возвращает значение f поздно, а переменная также устанавливается поздно, и сама задача может уже выполняться, видя нуль в ссылке.)
5 ответов
Когда повторяющаяся задача выдает исключение или ошибку, она помещается в будущее, и задача больше не повторяется. Вы можете выбросить RuntimeException или Error по вашему выбору.
Вместо использования анонимного внутреннего класса вы можете использовать именованный класс, который затем может иметь свойство для Future
объект, который вы получаете от Executor
когда вы планируете задачу.
abstract class FutureRunnable implements Runnable {
private Future<?> future;
/* Getter and Setter for future */
}
Когда вы планируете задачу, вы можете передать Future
к Runnable
,
FutureRunnable runnable = new FutureRunnable() {
public void run() {
if (/* abort condition */)
getFuture().cancel(false);
}
};
Future<?> future = executor.scheduleAtFixedRate(runnable, ...);
runnable.setFuture(future);
Возможно, вам придется убедиться, что задание не выполнено до Future
был установлен, потому что в противном случае вы получите NullPointerException
,
Кажется плохим дизайном для Runnable знать что-либо об исполнителе, в котором он работает, или выдавать ошибку, если достижение 10 не является состоянием ошибки, является хаком.
Можете ли вы сделать цикл до 10 вне планирования и выполнения? Это может потребовать использования исполнителя без планирования, так как вы будете планировать его вручную.
Вот другой способ, это даже потокобезопасный;
final Future<?>[] f = {null};
f[0]= scheduledExecutor.scheduleAtFixedRate(new Runnable() {
int count = 0;
public void run() {
System.out.println(count++);
if (count == 10) {
Future<?> future;
while(null==(future = f[0])) Thread.yield();//prevent exceptionally bad thread scheduling
future.cancel(false);
return;
//cancel self
}
}
}, 1, 1, TimeUnit.SECONDS);
Просто видел это сейчас... потому что я хотел сделать то же самое... вот мое решение, я подозреваю, что это потокобезопасно.
Сначала создайте контейнер для будущего:
public static class Cancel {
private ScheduledFuture<?> future;
public synchronized void setFuture(ScheduledFuture<?> future) {
this.future = future;
}
public synchronized void stop() {
LOG.debug("cancelling {}", future);
future.cancel(false);
}
}
И тогда будущий код:
final Cancel controller = new Cancel();
synchronized (controller) {
ScheduledFuture<?> future = scheduler.scheduleWithFixedDelay(() -> {
if (<CONTINUE RUNNING CONDITION) {
} else {
// STOP SCHEDULABLE FUTURE
controller.stop();
}
}, startTime, timeBetweenVisbilityChecks);
controller.setFuture(future);
}
}
Так что обратите внимание, что останов не будет вызываться до тех пор, пока не будет создано будущее и не будет установлено будущее на контроллере.
Имейте в виду, что Runnable - это внутренний аномальный класс, и он будет запущен в другом потоке.