Начальная задержка планировщика Java рассчитывается неправильно

Пытаюсь сделать так, чтобы раз в неделю в 8 утра программа отправляла сообщение. Он отправляет его в правильный день, но есть проблема с элементом времени. Если в тот день 7:58 утра, я хочу, чтобы оно было отправлено, оно будет ждать до 8 утра, чтобы отправить его, но если мне вообще придется перезапустить программу после 8 утра, она отправит ее снова с начальной задержкой 0 или отрицательное число, если день, когда его необходимо отправить, уже прошел. Изменение отрицательного числа на 0 приведет к отправке сообщения, что сохранит ошибку, поскольку время отправки прошло.

public class Reminder extends ListenerAdapter{
    @Override
    public void onReady(ReadyEvent event){
        final ZoneId zone = ZoneId.systemDefault();
        final ZoneId realzone = ZoneId.of(zone.getId());
        final ZonedDateTime zdt = ZonedDateTime.now(realzone);
        String day = String.valueOf(zdt.getDayOfWeek());
        String targetday = String.valueOf(DayOfWeek.WEDNESDAY);
        ZonedDateTime targettime = zdt.withHour(8).withMinute(0);
        ZonedDateTime currenttime = ZonedDateTime.now();
        long InitialDelay = currenttime.until(targettime, ChronoUnit.MILLIS);
        final Set<ZonedDateTime> targetTimes = Set.of(targettime);

        JDA bot = event.getJDA(); // <- JDA ...
        final long realChannelID = fakeID; //real
        final int period = 1000 * 60 * 60 * 24 * 7;
        TextChannel textChannel = event.getJDA().getTextChannelById(realChannelID);

        System.out.println(InitialDelay);

        ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);

        Runnable WeeklyMessageTask = () ->  {
                System.out.println("starting task");

                // for (ZonedDateTime time : targetTimes) {
                if (day.equals(targetday) && InitialDelay >= 0) {
                    System.out.println("Sending weekly message...");
                    switch (randomint()) {
                        case 1 -> textChannel2.sendMessage("Tiamat is Titillated...  Who is on board for putting an end to that?").queue();
                        case 2 -> textChannel2.sendMessage("It is Wednesday and the War rages... Who will join the fight?").queue();
                        case 3 -> textChannel2.sendMessage("Head count for tommorrow everyone?").queue();
                    }
                }
                if (!day.equals(targetday) && InitialDelay >= 0)
                System.out.println("NOT Sending weekly message...");
                System.out.println("ending task");
            };
        scheduler.scheduleAtFixedRate(WeeklyMessageTask, InitialDelay, period, TimeUnit.MILLISECONDS);
    }

    public int randomint(){
        Random rand = new Random();
        int max=3,min=1;
        int randomNum = rand.nextInt(max - min + 1) + min;
        return randomNum;
    }
}

Я попытался изменить оператор if, чтобы проверить, является ли значение отрицательным, чтобы предотвратить отправку дополнительных сообщений, которые не решили проблему. Я также пробовал разные единицы времени, чтобы увидеть, является ли это проблемой (от 7 дней до периода TimeUnit.Milliсекунды). Мне удалось воспроизвести эту проблему за пределами API JDA, поэтому я знаю, что это не проблема API.

Как мне лучше вычислить InitialDelay, чтобы избежать этой проблемы с scheduler.scheduleAtFixedRate()?

Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
1
0
68
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

Если он вызывается в 11 часов утра, ваш код в настоящее время отправляет задачу с отрицательной задержкой. Это означает, что запланированное действие выполняется немедленно (в 11 часов утра) и будет выполнено снова через неделю (в 11 часов утра...).

Чтобы надежно уведомлять в 8 утра, вам следует убедиться, что ваша задержка никогда не бывает отрицательной, отложив ее до следующей недели, если время уведомления на этой неделе уже прошло.

Я бы сделал это следующим образом:

    public void scheduleNotifications() {
        Thread.ofVirtual().start(() -> {
            for (;;) {
                sleepUntil(nextNotification());
                System.out.println("Hi!");
            }
        });
    }

    LocalDateTime nextNotification() {
        var next = LocalDate.now()
                .with(ChronoField.DAY_OF_WEEK, 3) // this week's Wednessday
                .atTime(8, 0);                    // at 8 AM
        if (next.isBefore(LocalDateTime.now())) { // if in the past
            next = next.plusWeeks(1);             // delay until next week
        }
        return next;
    }
    
    void sleepUntil(LocalDateTime time) {
        try {
            Thread.sleep(Duration.between(LocalDateTime.now(), time));
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }       
    }

Приведенное выше решение неоднократно рассчитывает задержку с учетом различной продолжительности недели (если дневное время заканчивается или начинается, неделя может быть на час длиннее или короче, чем обычно). То есть, если мы заботимся о постоянном времени уведомления в условиях летнего времени, мы не можем использовать ScheduledThreadPoolExecutor.scheduleAtFixedRate и должны сами управлять повторением. К счастью, виртуальные потоки облегчают эту задачу.

Если вы еще не можете использовать виртуальные потоки, вместо этого вы можете использовать ScheduledExecutor.schedule:

import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.temporal.ChronoField;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;

public class Test {

    ScheduledThreadPoolExecutor exec = new ScheduledThreadPoolExecutor(1);

    public void scheduleNotifications() {
        scheduleRepeatedly(() -> System.out.println("Hi!"), () -> nextNotification());
    }

    void scheduleRepeatedly(Runnable action, Supplier<LocalDateTime> timeSupplier) {
        var delay = Duration.between(LocalDateTime.now(), timeSupplier.get());
        exec.schedule(() -> {
            action.run();
            scheduleRepeatedly(action, timeSupplier);
        }, delay.toMillis(), TimeUnit.MILLISECONDS);
    }

    LocalDateTime nextNotification() {
        var next = LocalDate.now().with(ChronoField.DAY_OF_WEEK, 3).atTime(8, 0);
        if (next.isBefore(LocalDateTime.now())) {
            next = next.plusWeeks(1);
        }
        return next;
    }
}

Я получаю пару ошибок при работе с этой настройкой. Ему не нравится действие или время. Поставщик в Runnable action, Supplier<LocalDateTime> timeSupplier. действие в action.run(); и действие, timeSupplier также в scheduleRepeatedly(action, timeSupplier);. Я пропустил импорт? у меня есть импорт java.util.function.*;

abbatrombone 24.08.2024 22:15

public static void main(String[] args) { ScheduledThreadPoolExecutor exec = new ScheduledThreadPoolExecutor (1); void ScheduleRepeatedly (Runnable action, Поставщик <LocalDateTime> timeSupplier) {вар задержки = Duration.between (LocalDateTime.now(), timeSupplier.get()); exec.schedule(() -> { action.run(); ScheduleRepeatedly(action, timeSupplier); }, Delay.toMillis(), TimeUnit.MILLISCONDS); } } (методы находятся вне основного).

abbatrombone 24.08.2024 22:18

Если отредактировали, чтобы показать импорт, но там нет ничего необычного. Код компилируется для меня, как опубликовано.

meriton 24.08.2024 22:54

Кажется, моя проблема в том, что я не уверен, как лучше всего реализовать ваше решение в ScheduledExecutor.schedule.

abbatrombone 24.08.2024 23:24

Другие вопросы по теме

Создайте запланированное задание, которое запускается первого числа каждого месяца
Запланируйте задачу на второе воскресенье месяца или на третье и четвертое воскресенье и субботу
Используя Postgres/Flask, как я могу запросить следующее появление запланированной задачи, когда дни/часы/минуты хранятся как целые числа в своих собственных столбцах?
Автоматическое завершение обработки заказов WooCommerce с бронированиями, дата начала которых уже прошла
Как обеспечить выполнение хрон-задания точно на каждой возможной 5-минутной отметке (0:00, 0:05, 0:10) независимо от времени начала хрон-задания?
Запланированное задание Synology не выполняет простую команду с сообщением «нет такого файла»
Приложение имеет несколько экземпляров. Существует задание, которое будет выполняться в определенное время. как я могу заставить задание выполняться только одним экземпляром?
Spring загрузка аннотации @Async с @Scheduled
Расписание Java для периодического запуска против Thread.sleep
Автоматическое копирование новых файлов из одной папки в другую папку