Остановить периодическую задачу из самой задачи, выполняемой в 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 late, а переменная также устанавливается поздно, а сама задача может уже выполняться, видя в ссылке null.)


person akarnokd    schedule 05.02.2011    source источник
comment
Почему бы не создать исключение StopIteration, которое можно перехватить, чтобы удалить вызов исполнителя извне? Насколько мне известно, Callable не знают, к какому исполнителю они добавлены.   -  person Tim    schedule 06.02.2011
comment
самый простой и довольно уродливый способ - выбросить исключение :)   -  person bestsss    schedule 06.02.2011


Ответы (5)


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

person Peter Lawrey    schedule 05.02.2011
comment
Да, это вариант. Надеюсь на более элегантное решение. - person akarnokd; 06.02.2011
comment
Это лучший способ сделать это. Если вы хотите сделать его красивым, объявите класс, расширяющий RuntimeException, который правильно описывает цель, например TaskComplete. - person Jed Wesley-Smith; 06.02.2011
comment
@ kd304, если вы думаете: именно по этой причине создаются исключения, чтобы сигнализировать об «исключительном» состоянии, я не видел ответа Питера, когда я оставил комментарий. Это ответ на еду (править: хорошие, чертовски неуместные пальцы) :) - person bestsss; 06.02.2011
comment
Не приведет ли это к утечке памяти в длительном процессе, поскольку ссылка на runnable по-прежнему будет сохраняться в планированномExecutor? - person Abhinav Sarkar; 19.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.

person Reboot    schedule 05.02.2011
comment
NPE эффективно отменит задачу так же хорошо :) - person bestsss; 06.02.2011

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

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

person Alb    schedule 05.02.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);
person bestsss    schedule 06.02.2011

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

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

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

person Michael Wiles    schedule 30.04.2015