英文:
Run a task every 30 min during time window
问题
我对Java还不太熟悉,但我正在尝试解决在工作日的上午5点到下午5点每30分钟运行一次任务的问题。我四处查找,发现可能可以使用这个类:
ScheduledExecutorService ses = Executors.newSingleThreadScheduledExecutor();
我在考虑可以使用这个库来调用一个函数,并在一个if语句中包装函数调用,以检查它是否在9点到5点之间。我唯一担心的问题是,在它不做任何事情的时候,线程会在整个夜晚运行。想知道我是否应该担心这个问题,或者是否有一种方法可以让线程在最后一次运行后休眠约8个小时,直到第二天。
英文:
I am quite new to java, but I am trying to work out running a task every 30 min during the hours 5 AM to 5 PM during the week. I looked around and found that I could possible use this class
ScheduledExecutorService ses = Executors.newSingleThreadScheduledExecutor();
I am thinking I could use the library to call a function and wrap the function call in an if statement to check if it falls between the 9 to 5 time. My only issue about this is running a thread thru out the night while it is not doing anything. Was wondering if that is something I should worry about or is there a way I can just have the thread sleep after its last run for 8 or so hours until the next day
答案1
得分: 1
是的,请使用ScheduledExecutorService
。
注意schedule
方法接受一对参数,用于指定要等待多长时间并且粒度是多少时间来运行任务。
因此,在启动应用程序时,计算距离下一次任务开始的时间。使用DayOfWeek
来判断当前时刻是否是周一至周五。检查当前LocalTime
是否在早上5点到下午5点之间。使用ZonedDateTime
来确定时间点。
要获取下一个工作日,需要将ThreeTen-Extra库添加到项目中,以获取其实现的下一个工作日调整器。这些内容在Stack Overflow上已经多次讨论过。搜索以获取更多信息并查看示例代码。
类似于下面这段未经测试的代码。(风险自担。)
public Duration untilNextRun(final ZoneId zoneId) {
// 计算等待下一个任务运行的时间量。
// 在周一至周五的工作时间内(早上5点到下午5点),我们希望每半小时运行一次。
// 在这些时间之外,我们必须等到下一个早上5点的开始时间。
Set<DayOfWeek> weekDays = EnumSet.of(DayOfWeek.MONDAY, DayOfWeek.TUESDAY, DayOfWeek.WEDNESDAY, DayOfWeek.THURSDAY, DayOfWeek.FRIDAY);
LocalTime startTime = LocalTime.of(5, 0);
LocalTime stopTime = LocalTime.of(17, 0);
ZonedDateTime now = ZonedDateTime.now(zoneId);
DayOfWeek currentDayOfWeek = now.getDayOfWeek();
LocalDate currentDate = now.toLocalDate();
LocalTime currentTime = now.toLocalTime();
if (weekDays.contains(currentDayOfWeek)) {
// ... 省略其他部分 ...
} else {
// ... 省略其他部分 ...
}
}
尝试使用该方法:
Duration duration = this.untilNextRun(ZoneId.of("America/Los_Angeles"));
System.out.println("ZonedDateTime.now(…) = " + ZonedDateTime.now(ZoneId.of("America/Los_Angeles")));
System.out.println("duration = " + duration);
将Duration
转换为分钟或秒,以便在计划的执行器服务上安排任务:
ses.schedule(task, duration.toSeconds(), TimeUnit.SECONDS);
作为任务的一部分,它应该调度另一个任务的执行。如果任务完成的时刻仍在目标时间范围内,就安排一个30分钟的延迟。如果完成时刻超出目标时间范围,就安排一个足够长的延迟,使我们能够等到下一个目标开始时间。对于周一至周五,这个更长的延迟可能是隔夜,或者是在周末。
将任务定义为Runnable
或Callable
,类似于以下未尝试的代码:
Runnable task = () -> {
System.out.println("Task is running at " + Instant.now());
// ... 做你的工作 ...
// 调度任务再次运行。
ZoneId z = ZoneId.of("America/Montreal");
Duration d = this.untilNextRun(z);
ses.schedule(task, d, TimeUnit.SECONDS);
};
英文:
Yes, use a ScheduledExecutorService
.
Notice how the schedule
method takes a pair of arguments for an amount and a granularity of an amount of time to wait before running the task.
So when you start your app, see how long until when you want to start the first task. Use DayOfWeek
to see if the current moment is Monday-Friday. Check to see if the current LocalTime
is between 5 AM to 5 PM. Use ZonedDateTime
to determine moments.
To get the next working day, add the ThreeTen-Extra library to your project to get its next-working-day temporal adjuster implementation. All this has been covered many times already on Stack Overflow. So search to learn more and see example code.
Something like this untested code. (Use at your own risk.)
public Duration untilNextRun ( final ZoneId zoneId )
{
// Calculate the amount to wait untli the next run of some task.
// From Monday-Friday, 5 AM to 5 PM, we want to run every half-hour.
// Outside those hours, we must wait until the next 5 AM starting time.
Set < DayOfWeek > weekDays = EnumSet.of( DayOfWeek.MONDAY , DayOfWeek.TUESDAY , DayOfWeek.WEDNESDAY , DayOfWeek.THURSDAY , DayOfWeek.FRIDAY );
LocalTime startTime = LocalTime.of( 5 , 0 );
LocalTime stopTime = LocalTime.of( 17 , 0 );
ZonedDateTime now = ZonedDateTime.now( zoneId );
DayOfWeek currentDayOfWeek = now.getDayOfWeek();
LocalDate currentDate = now.toLocalDate();
LocalTime currentTime = now.toLocalTime();
if ( weekDays.contains( currentDayOfWeek ) )
{
if ( currentTime.isBefore( startTime ) )
{
// On a weekday, but too early to start a regular 30-minute wait. Wait until 5 AM on this same date.
ZonedDateTime zdt = ZonedDateTime.of( currentDate , startTime , zoneId );
Duration duration = Duration.between( now.toInstant() , zdt.toInstant() );
return Objects.requireNonNull( duration );
} else if ( currentTime.isBefore( stopTime ) )
{
// On a weekday, within our working hours, so start a regular 30-minute wait.
return Duration.ofMinutes( 30 );
} else if ( ! currentTime.isBefore( stopTime ) )
{
// Too late in the day to start another 30-minute wait. So wait until the next non-weekend day.
LocalDate nextWorkingLocalDate = currentDate.with( Temporals.nextWorkingDay() ) ;
ZonedDateTime zdt = ZonedDateTime.of( nextWorkingLocalDate , startTime , zoneId );
Duration duration = Duration.between( now.toInstant() , zdt.toInstant() );
return Objects.requireNonNull( duration );
} else
{
throw new IllegalStateException( "Should not have ever reached this point. Message # 7818816c-4b5c-47e3-a0a5-d7c8c999f071." );
}
} else
{
// Not on a weekday (on a weekend instead), so we cannot start another 30-minute wait. So wait until the next non-weekend day.
LocalDate nextWorkingLocalDate = currentDate.with( Temporals.nextWorkingDay() ) ;
ZonedDateTime zdt = ZonedDateTime.of( nextWorkingLocalDate , startTime , zoneId );
Duration duration = Duration.between( now.toInstant() , zdt.toInstant() );
return Objects.requireNonNull( duration );
}
}
Try using that method.
Duration duration = this.untilNextRun( ZoneId.of( "America/Los_Angeles" ) );
System.out.println( "ZonedDateTime.now(…) = " + ZonedDateTime.now( ZoneId.of( "America/Los_Angeles" ) ) );
System.out.println( "duration = " + duration );
>ZonedDateTime.now(…) = 2020-04-06T18:29:41.880591-07:00[America/Los_Angeles]
>duration = PT10H30M18.124981S
To schedule your task on the scheduled executor service, convert the Duration
to a number of minutes or seconds.
ses.schedule( task , duration.toSeconds() , TimeUnit.SECONDS ) ;
As part of that task’s duties, it should schedule another execution of the task. If the moment when the task finishes is still within the target hours, schedule a delay of 30 minutes. If the finishing moment is outside the target, schedule with a delay long enough to take us up to the next starting target time. That longer delay might be overnight for M-F, or over the weekend.
Define your task as a Runnable
or Callable
. Something like this untried code.
Runnable task = ( ) -> {
System.out.println( "Task is running at " + Instant.now() );
// … do your work …
// Schedule task to run again.
ZoneId z = ZoneId.of( "America/Montreal" );
Duration d = this.untilNextRun( z );
ses.schedule( task , d , TimeUnit.SECONDS ));
};
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论