использование ScheduledExecutorService для запуска и остановки таймера

Из моих чтений кажется, что ScheduledExecutorService — это правильный способ запуска и остановки таймеров в Java.

Мне нужно портировать некоторый код, который запускает и останавливает таймер. Это не периодический таймер. Этот код останавливает таймер перед его запуском. Таким образом, каждый запуск на самом деле является перезапуском(). Я ищу правильный способ сделать это с помощью ScheduledExecutorService. Вот что я придумал. Ищу комментарии и понимание того, что мне не хватает:

ScheduledExecutorService _Timer = Executors.newScheduledThreadPool(1);
ScheduledFuture<?> _TimerFuture = null;

private boolean startTimer() {
    try {
        if (_TimerFuture != null) {
            //cancel execution of the future task (TimerPopTask())
            //If task is already running, do not interrupt it.
            _TimerFuture.cancel(false);
        }

        _TimerFuture = _Timer.schedule(new TimerPopTask(), 
                                       TIMER_IN_SECONDS, 
                                       TimeUnit.SECONDS);
        return true;
    } catch (Exception e) {
        return false;
    }
}

private boolean stopTimer() {
    try {
        if (_TimerFuture != null) {
            //cancel execution of the future task (TimerPopTask())
            //If task is already running, interrupt it here.
            _TimerFuture.cancel(true);
        }

        return true;
    } catch (Exception e) {
        return false;
    }
}

private class TimerPopTask implements Runnable  {  
    public void run ()   {  
        TimerPopped();
    }  
}

public void TimerPopped () {
    //Do Something
}

тиа, рубль


person rouble    schedule 23.06.2010    source источник
comment
Код в том виде, в котором он написан, не компилируется: _batchTimer в startTimer() нигде не объявлен. Было бы полезно, если бы вы могли немного подробнее остановиться на ожидаемом поведении: какие значения возвращаются из startTimer и stopTimer? Почему вы хотите, чтобы start/stopTimer потенциально блокировал вызовы?   -  person Sbodd    schedule 23.06.2010
comment
@Sbodd, когда я запускаю таймер, если он уже запущен, я хочу его остановить, чтобы два таймера не срабатывали одновременно. Я думаю, что вместо get() мне просто нужно вызвать cancel() в будущем экземпляре.   -  person rouble    schedule 23.06.2010


Ответы (1)


Это похоже на проблему:

private boolean startTimer() {
    // ......
        if (_TimerFuture != null) {
            _TimerFuture.cancel(false);
        }

        _TimerFuture = _Timer.schedule(new TimerPopTask(), 
                                       TIMER_IN_SECONDS, 
                                       TimeUnit.SECONDS);
    // ......
}

Поскольку вы передаете false для отмены, старый _TimerFuture может не быть отменен, если задача уже запущена. В любом случае создается новый (но он не будет работать одновременно, потому что ваш ExecutorService имеет фиксированный размер пула потоков, равный 1). В любом случае, это не похоже на желаемое поведение перезапуска таймера при вызове startTimer().

Я бы немного переделал. Я бы сделал экземпляр TimerPopTask тем, что вы «отменяете», и оставил бы ScheduledFutures в покое после их создания:

private class TimerPopTask implements Runnable  {
    //volatile for thread-safety
    private volatile boolean isActive = true;  
    public void run ()   {  
        if (isActive){
            TimerPopped();
        }
    }  
    public void deactivate(){
        isActive = false;
    }
}

тогда я бы сохранил экземпляр TimerPopTask, а не экземпляр ScheduledFuture, и таким образом переупорядочил метод startTimer:

private TimerPopTask timerPopTask;

private boolean startTimer() {
    try {
        if (timerPopTask != null) {
            timerPopTask.deactivate();
        }

        timerPopTask = new TimerPopTask();
        _Timer.schedule(timerPopTask, 
                        TIMER_IN_SECONDS, 
                        TimeUnit.SECONDS);
        return true;
    } catch (Exception e) {
        return false;
    }
}

(Аналогичная модификация метода stopTimer().)

Возможно, вы захотите увеличить количество потоков, если вы действительно ожидаете необходимости «перезапустить» таймер до истечения срока действия текущего таймера:

private ScheduledExecutorService _Timer = Executors.newScheduledThreadPool(5);

Возможно, вы захотите использовать гибридный подход, сохраняя ссылки как на текущий TimerPopTask, как я описал, так и на текущий ScheduledFuture, и приложите все усилия, чтобы отменить его и освободить поток, если это возможно, понимая, что его отмена не гарантируется.

(Примечание: все это предполагает, что вызовы методов startTimer() и stopTimer() ограничены одним основным потоком, и между потоками совместно используются только TimerPopTask экземпляров. В противном случае вам потребуются дополнительные меры безопасности.)

person Scott Bale    schedule 23.06.2010
comment
хорошая точка зрения. Позвольте мне спросить вас об этом, скажем, выполнялась задача. Создается новая задача, запуск которой запланирован на TIMER_IN_SECONDS сек. позже. Когда начинается это время (TIMER_IN_SECONDS секунд)? Начинается ли он прямо тогда, когда вызывается schedule() ИЛИ он начинается, когда в пуле потоков есть свободный поток? - person rouble; 24.06.2010
comment
Задержка TIMER_IN_SECONDS начинается прямо сейчас, когда вызывается schedule(). Задача гарантированно запустится не раньше задержки, однако она может начаться позже (отчасти в зависимости от доступности потока). Из javadoc для ScheduledThreadPoolExecutor: отложенные задачи выполняются не раньше, чем они будут включены, но без каких-либо гарантий в реальном времени о том, когда после того, как они будут включены, они начнутся. - person Scott Bale; 24.06.2010