How to stop a runnable scheduled to run again after a certain number of executions

Situation

I have a runnable. I have a class that schedules this Runnable to be executed using ScheduledExecutorService with scheduleWithFixedDelay .

goal

I want to change this class to schedule Runnable to run for a fixed delay, either indefinitely, or until it is run a certain number of times, depending on some parameter that is passed to the constructor.

If possible, I would like to use the same Runnable as conceptually the same as what should be run.

Possible approaches

Approach No. 1

You have two Runnables, one of which cancels the schedule after several executions (in which it is stored), and the other:

public class MyClass{ private ScheduledExecutorService scheduler = Executors.newSingleThreadScheduledExecutor(); public enum Mode{ INDEFINITE, FIXED_NO_OF_TIMES } public MyClass(Mode mode){ if(mode == Mode.INDEFINITE){ scheduler.scheduleWithFixedDelay(new DoSomethingTask(), 0, 100, TimeUnit.MILLISECONDS); }else if(mode == Mode.FIXED_NO_OF_TIMES){ scheduler.scheduleWithFixedDelay(new DoSomethingNTimesTask(), 0, 100, TimeUnit.MILLISECONDS); } } private class DoSomethingTask implements Runnable{ @Override public void run(){ doSomething(); } } private class DoSomethingNTimesTask implements Runnable{ private int count = 0; @Override public void run(){ doSomething(); count++; if(count > 42){ // Cancel the scheduling. // Can you do this inside the run method, presumably using // the Future returned by the schedule method? Is it a good idea? } } } private void doSomething(){ // do something } } 

I would rather just run Runnable to execute the doSomething method. Linking planning to runnable seems wrong. What do you think about it?

Approach No. 2

You have one Runnable to execute code that we want to run periodically. It has a separate scheduled runnable that checks how many times the first runnable has started and cancels when it reaches a certain amount. This may be inaccurate, as it will be asynchronous. It seems a little cumbersome. What do you think about it?

Approach No. 3

Extend ScheduledExecutorService and add the "scheduleWithFixedDelayNTimes" method. Maybe such a class already exists? I am currently using Executors.newSingleThreadScheduledExecutor(); to get an instance of ScheduledExecutorService. I would presumably have to implement similar functions to instantiate an extended ScheduledExecutorService. It can be tricky. What do you think about it?

There is no approach to the scheduler [Change]

I could not use the scheduler. Instead, I could have something like:

 for(int i = 0; i < numTimesToRun; i++){ doSomething(); Thread.sleep(delay); } 

And run it in some thread. What do you think about it? You can potentially use runnable and directly call the run method.




Any suggestions are welcome. I am looking for debate to find a “best practice” way to achieve my goal.

+56
java scheduled-tasks scheduling
Sep 01 '11 at 10:52
source share
7 answers

You can use the cancel () method for Future. From javadocs scheduleAtFixedRate

 Otherwise, the task will only terminate via cancellation or termination of the executor 

Here is an example code that wraps Runnable in another, which tracks the number of times the original starts and cancels it after N times.

 public void runNTimes(Runnable task, int maxRunCount, long period, TimeUnit unit, ScheduledExecutorService executor) { new FixedExecutionRunnable(task, maxRunCount).runNTimes(executor, period, unit); } class FixedExecutionRunnable implements Runnable { private final AtomicInteger runCount = new AtomicInteger(); private final Runnable delegate; private volatile ScheduledFuture<?> self; private final int maxRunCount; public FixedExecutionRunnable(Runnable delegate, int maxRunCount) { this.delegate = delegate; this.maxRunCount = maxRunCount; } @Override public void run() { delegate.run(); if(runCount.incrementAndGet() == maxRunCount) { boolean interrupted = false; try { while(self == null) { try { Thread.sleep(1); } catch (InterruptedException e) { interrupted = true; } } self.cancel(false); } finally { if(interrupted) { Thread.currentThread().interrupt(); } } } } public void runNTimes(ScheduledExecutorService executor, long period, TimeUnit unit) { self = executor.scheduleAtFixedRate(this, 0, period, unit); } } 
+61
Sep 04 2018-11-11T00:
source share

Quoted from the API description ( ScheduledExecutorService.scheduleWithFixedDelay ):

Creates and performs a periodic action that becomes active primarily after a given initial delay, and then with a specified delay between the termination of one execution and the beginning of the next. If any task execution encounters an exception, subsequent executions are suppressed. Otherwise, the task will be terminated only by canceling or terminating the contractor.

So, the easiest way would be to "just throw an exception" (even if this is considered bad practice):

 static class MyTask implements Runnable { private int runs = 0; @Override public void run() { System.out.println(runs); if (++runs >= 20) throw new RuntimeException(); } } public static void main(String[] args) { ScheduledExecutorService s = Executors.newSingleThreadScheduledExecutor(); s.scheduleWithFixedDelay(new MyTask(), 0, 100, TimeUnit.MILLISECONDS); } 
+8
Sep 01 '11 at 11:21
source share

The sbridges solution is still the cleanest so far, except that you mentioned that it is not responsible for handling the number of executions of Runnable itself. This should not concern this; instead, repetitions should be a parameter of the class that handles the planning. To achieve this, I would suggest the following design, which introduces a new artist class for Runnables . The class provides two publicly available task scheduling methods, which are standard Runnables , with finite or infinite repetition. The same Runnable can be passed for finite and infinite planning, if necessary (which is not possible with all the proposed solutions that extend the Runnable class to provide finite repetitions). The end repetition cancellation handling is fully encapsulated in the scheduler class:

 class MaxNScheduler { public enum ScheduleType { FixedRate, FixedDelay } private ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor(); public ScheduledFuture<?> scheduleInfinitely(Runnable task, ScheduleType type, long initialDelay, long period, TimeUnit unit) { return scheduleNTimes(task, -1, type, initialDelay, period, unit); } /** schedule with count repetitions */ public ScheduledFuture<?> scheduleNTimes(Runnable task, int repetitions, ScheduleType type, long initialDelay, long period, TimeUnit unit) { RunnableWrapper wrapper = new RunnableWrapper(task, repetitions); ScheduledFuture<?> future; if(type == ScheduleType.FixedDelay) future = executorService.scheduleWithFixedDelay(wrapper, initialDelay, period, TimeUnit.MILLISECONDS); else future = executorService.scheduleAtFixedRate(wrapper, initialDelay, period, TimeUnit.MILLISECONDS); synchronized(wrapper) { wrapper.self = future; wrapper.notify(); // notify wrapper that it nows about it future (pun intended) } return future; } private static class RunnableWrapper implements Runnable { private final Runnable realRunnable; private int repetitions = -1; ScheduledFuture<?> self = null; RunnableWrapper(Runnable realRunnable, int repetitions) { this.realRunnable = realRunnable; this.repetitions = repetitions; } private boolean isInfinite() { return repetitions < 0; } private boolean isFinished() { return repetitions == 0; } @Override public void run() { if(!isFinished()) // guard for calls to run when it should be cancelled already { realRunnable.run(); if(!isInfinite()) { repetitions--; if(isFinished()) { synchronized(this) // need to wait until self is actually set { if(self == null) { try { wait(); } catch(Exception e) { /* should not happen... */ } } self.cancel(false); // cancel gracefully (not throwing InterruptedException) } } } } } } } 

In fairness, it follows that the repetition control logic still exists with Runnable , but it is Runnable completely internal to MaxNScheduler , while the Runnable task that was submitted for scheduling should not concern itself with the nature of scheduling. Also, this problem can be easily ported to the scheduler, if necessary, by providing some callback every time RunnableWrapper.run executed. This will complicate the code a bit and add the need to save some RunnableWrapper map and corresponding repetitions, so I decided to save the counters in the RunnableWrapper class.

I also added some wrapper synchronization when setting up self. This is necessary as theoretically, when the completion of execution, I may not have been assigned yet (theoretical scenario, but only for one repetition).

The cancellation is handled gracefully without throwing an InterruptedException , and if the next round is executed before it is canceled, RunnableWrapper will not call the base Runnable .

+5
Sep 11 '11 at 3:11
source share

Your first approach seems OK. You could combine both types of runnables by passing the mode object to its constructor (or passing -1 as the maximum number of times it should be executed), and use this mode to determine whether runnable should be canceled or not:

 private class DoSomethingNTimesTask implements Runnable{ private int count = 0; private final int limit; /** * Constructor for no limit */ private DoSomethingNTimesTask() { this(-1); } /** * Constructor allowing to set a limit * @param limit the limit (negative number for no limit) */ private DoSomethingNTimesTask(int limit) { this.limit = limit; } @Override public void run(){ doSomething(); count++; if(limit >= 0 && count > limit){ // Cancel the scheduling } } } 

You will need to pass the planned future to your task so that it can cancel itself, or you can throw an exception.

+1
01 Sep 2018-11-11T00:
source share

Here is my suggestion (I believe that it handles all the cases mentioned in the question):

 public class RepeatedScheduled implements Runnable { private int repeatCounter = -1; private boolean infinite; private ScheduledExecutorService ses; private long initialDelay; private long delay; private TimeUnit unit; private final Runnable command; private Future<?> control; public RepeatedScheduled(ScheduledExecutorService ses, Runnable command, long initialDelay, long delay, TimeUnit unit) { this.ses = ses; this.initialDelay = initialDelay; this.delay = delay; this.unit = unit; this.command = command; this.infinite = true; } public RepeatedScheduled(ScheduledExecutorService ses, Runnable command, long initialDelay, long delay, TimeUnit unit, int maxExecutions) { this(ses, command, initialDelay, delay, unit); this.repeatCounter = maxExecutions; this.infinite = false; } public Future<?> submit() { // We submit this, not the received command this.control = this.ses.scheduleWithFixedDelay(this, this.initialDelay, this.delay, this.unit); return this.control; } @Override public synchronized void run() { if ( !this.infinite ) { if ( this.repeatCounter > 0 ) { this.command.run(); this.repeatCounter--; } else { this.control.cancel(false); } } else { this.command.run(); } } } 

In addition, it allows the outside to stop everything from Future returned by the submit() method.

Using:

 Runnable MyRunnable = ...; // Repeat 20 times RepeatedScheduled rs = new RepeatedScheduled( MySes, MyRunnable, 33, 44, TimeUnit.SECONDS, 20); Future<?> MyControl = rs.submit(); ... 
+1
Sep 04 '11 at 17:02
source share

I searched exactly org.springframework.scheduling.Trigger same functionality and chose org.springframework.scheduling.Trigger .

Below is an example of full testing (sorry if the code has too much data) applicationContext.xml

 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:task="http://www.springframework.org/schema/task" xmlns:util="http://www.springframework.org/schema/util" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context/ http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/util/ http://www.springframework.org/schema/util/spring-util.xsd http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-3.0.xsd"> <bean id="blockingTasksScheduler" class="org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler"> <property name="poolSize" value="10" /> </bean> <task:scheduler id="deftaskScheduler" pool-size="10" /> </beans> 

JAVA

 package com.alz.springTests.schedulerTest; import java.time.LocalDateTime; import java.time.ZoneId; import java.time.temporal.ChronoUnit; import java.util.Date; import java.util.concurrent.ScheduledThreadPoolExecutor; import java.util.concurrent.ThreadLocalRandom; import java.util.concurrent.atomic.AtomicInteger; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import org.springframework.scheduling.TaskScheduler; import org.springframework.scheduling.Trigger; import org.springframework.scheduling.TriggerContext; import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler; public class ScheduledTest { private static ApplicationContext applicationContext; private static TaskScheduler taskScheduler; private static final class SelfCancelableTask implements Runnable, Trigger { Date creationTime = new Date(); AtomicInteger counter = new AtomicInteger(0); private volatile boolean shouldStop = false; private int repeatInterval = 3; //seconds @Override public void run() { log("task: run started"); // simulate "doing job" started int sleepTimeMs = ThreadLocalRandom.current().nextInt(500, 2000+1); log("will sleep " + sleepTimeMs + " ms"); try { Thread.sleep(sleepTimeMs); } catch (InterruptedException e) { e.printStackTrace(); } // "doing job" finished int i = counter.incrementAndGet(); if (i > 5) { //cancel myself logErr("Attempts exceeded, will mark as shouldStop"); shouldStop = true; } else { log("task: executing cycle #"+i); } } @Override public Date nextExecutionTime(TriggerContext triggerContext) { log("nextExecutionTime: triggerContext.lastActualExecutionTime() " + triggerContext.lastActualExecutionTime()); log("nextExecutionTime: triggerContext.lastCompletionTime() " + triggerContext.lastCompletionTime()); log("nextExecutionTime: triggerContext.lastScheduledExecutionTime() " + triggerContext.lastScheduledExecutionTime()); if (shouldStop) return null; if (triggerContext.lastCompletionTime() == null) { LocalDateTime ldt = creationTime.toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime().plus(repeatInterval, ChronoUnit.SECONDS); return Date.from(ldt.atZone(ZoneId.systemDefault()).toInstant()); } else { LocalDateTime ldt = triggerContext.lastCompletionTime().toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime().plus(repeatInterval, ChronoUnit.SECONDS); return Date.from(ldt.atZone(ZoneId.systemDefault()).toInstant()); } } } private static void log(String log) { System.out.printf("%s [%s] %s\r\n", LocalDateTime.now(), Thread.currentThread(), log); } private static void logErr(String log) { System.err.printf("%s [%s] %s\r\n", LocalDateTime.now(), Thread.currentThread(), log); } public static void main(String[] args) { log("main: Stated..."); applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml"); taskScheduler = (TaskScheduler) applicationContext.getBean("blockingTasksScheduler"); ScheduledThreadPoolExecutor scheduledThreadPoolExecutor = ((ThreadPoolTaskScheduler)taskScheduler).getScheduledThreadPoolExecutor(); SelfCancelableTask selfCancelableTask = new SelfCancelableTask(); taskScheduler.schedule(selfCancelableTask, selfCancelableTask); int waitAttempts = 0; while (waitAttempts < 30) { log("scheduledPool pending tasks: " + scheduledThreadPoolExecutor.getQueue().size()); try { Thread.sleep(1*1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } waitAttempts++; } log("main: Done!"); } } 
0
Mar 15 '19 at 13:35
source share

For use Future.get() such as polling up to a specific timeout, we can come up with a simpler solution using Future.get() .

 /* Define task */ public class Poll implements Runnable { @Override public void run() { // Polling logic } } /* Create executor service */ ScheduledExecutorService executorService = Executors.newScheduledThreadPool(5); /* Schedule task - poll every 500ms */ ScheduledFuture<?> future = executorService.scheduleAtFixedRate(new Poll(), 0, 500, TimeUnit.MILLISECONDS); /* Wait till 60 sec timeout */ try { future.get(60, TimeUnit.SECONDS); } catch (TimeoutException e) { scheduledFuture.cancel(false); // Take action on timeout } 
0
Jul 11 '19 at 1:46
source share



All Articles