Остановить периодическую задачу изнутри самой задачи, выполняемой в ScheduledExecutorService - PullRequest
21 голосов
/ 06 февраля 2011

Есть ли хороший способ остановить повторение задачи из самой задачи при запуске в 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 возвращает значение с опозданием, а переменная также устанавливается с опозданием, и сама задача может уже выполняться, видя нуль в ссылке.)

Ответы [ 5 ]

13 голосов
/ 06 февраля 2011

Когда повторяющееся задание вызывает исключение или ошибку, оно помещается в будущее, и задание больше не повторяется. Вы можете выбросить RuntimeException или Error по вашему выбору.

4 голосов
/ 06 февраля 2011

Вместо использования анонимного внутреннего класса вы можете использовать именованный класс, который затем может иметь свойство для объекта 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.

2 голосов
/ 06 февраля 2011

Плохой дизайн для Runnable: знать что-либо об исполнителе, в котором он запущен, или выдавать ошибку, если достижение 10 не является состоянием ошибки, является хаком.

Можете ли вы сделать цикл до 10 за пределами планирования и выполнения? Для этого может потребоваться использование исполнителя без планирования, поскольку вы сами будете планировать его вручную.

1 голос
/ 30 апреля 2015

Только что увидел это сейчас ... потому что я хотел сделать то же самое ... вот мое решение, я подозреваю, что это потокобезопасно.

Сначала создайте контейнер для будущего:

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 - это аномичный внутренний класс, и он будет запущен в другом потоке.

1 голос
/ 06 февраля 2011

Вот еще один способ, это даже потокобезопасно;

    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);
...