我正在尝试让程序每周早上 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()
出现此问题?
如果在上午 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;
}