Подтвердить что ты не робот

Остановка периодической задачи из самой задачи, выполняемой в 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(), но как я могу остановить повторение в указанном месте? (Прохождение Будущее через AtomicReference небезопасно, потому что есть потенциальное окно, когда schedAtFixedRate возвращает f поздно, и переменная также установлена ​​слишком поздно, и сама задача может уже выполняться, видя нуль в ссылке.)

4b9b3361

Ответ 1

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

Ответ 2

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

Ответ 3

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

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

Ответ 4

Вот еще один способ, что даже Thread safe;

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

Ответ 5

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

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

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