Java 调度程序初始延迟未正确计算

问题描述 投票:0回答:1

我正在尝试让程序每周早上 8 点发送一条消息。它确实在正确的日期发送,但时间元素存在问题。如果是当天上午 7:58,我希望它发送,它将等到上午 8 点才发送,但如果我必须在上午 8 点之后重新启动程序,它会再次发送,初始延迟为 0 或 a如果超过了需要发送的日期,则为负数。将负数更改为 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.Milliseconds 为周期)。我已经能够在 JDA 的 API 之外复制这个问题,所以我知道这不是 API 的问题。

如何更好地计算 InitialDelay,以避免

scheduler.scheduleAtFixedRate()
出现此问题?

java scheduled-tasks
1个回答
0
投票

如果在上午 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
来代替:

    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;
    }
© www.soinside.com 2019 - 2024. All rights reserved.