Daniel Alexiuc
Daniel Alexiuc

Reputation: 13240

Cancel a scheduled fixed rate task depending on the result of the task

I'm using Spring's TaskScheduler to schedule a periodic task.

ScheduledFuture scheduleAtFixedRate(Runnable task, long period);

I understand that I can call cancel() on the ScheduledFuture to stop the recurring task from being executed. But I'd like to cancel the recurring scheduled task depending on the result of the execution of the task, and am not sure how to best do that.

Does the ScheduledFuture give me access to the result of EACH executed task? Or do I need some sort of task listener that can keep a reference to this ScheduledFuture, and cancel it that way? Or something else?

Upvotes: 3

Views: 5123

Answers (3)

gpwclark
gpwclark

Reputation: 66

Here is a modified beeper example that demonstrates how to make a decision after EACH scheduled task. I used a latch so I could wrap it in a test case and assert the right thing happened (and of course to keep the test runner's thread from stopping). I also changed the intervals (it beeps every 10ms after an initial 10ms delay) so the test could be copied, pasted, and executed in a second as opposed to an hour.


import org.junit.Assert;
import org.junit.Test;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

public class BeeperTest {
    class BeeperControl {
        private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1, (runnable) -> {
            Thread thread = new Thread(runnable);
            thread.setName("MyAwesomeBeeperTestThread");
            thread.setDaemon(true);
            return thread;
        });

        public void beepTheNumberOfTimesIWant(CountDownLatch latch) {
            long initialDelay = 10;
            long frequency = 10;
            TimeUnit unit = TimeUnit.MILLISECONDS;
            final int numberOfTimesToBeep = 5;
            AtomicInteger numberOfTimesIveBeeped = new AtomicInteger(0);
            final ScheduledFuture[] beeperHandle = new ScheduledFuture[1];
            beeperHandle[0] = scheduler.scheduleAtFixedRate(() -> {
                    if (numberOfTimesToBeep == numberOfTimesIveBeeped.get()) {
                        System.out.println("Let's get this done!");
                        latch.countDown();
                        beeperHandle[0].cancel(false);
                    }
                    else {
                        System.out.println("beep");
                        numberOfTimesIveBeeped.incrementAndGet();
                    }
                }, initialDelay, frequency, unit);
        }
    }

    @Test
    public void beepPlease() throws InterruptedException {
        CountDownLatch latch = new CountDownLatch(1);
        BeeperControl control = new BeeperControl();
        control.beepTheNumberOfTimesIWant(latch);
        boolean completed = latch.await(1, TimeUnit.SECONDS);
        Assert.assertTrue("Beeper should be able to finish beeping" +
            "within allotted await time.", completed);
    }
}

Upvotes: 0

Snekse
Snekse

Reputation: 15789

Keep a handle or the original fixed rate ScheduledFuture, then when the condition arises where you want to cancel it, schedule a new task that does the cancel.

You might also be able to do something with a RunnableScheduledFuture.

From the ScheduledExecutorService docs

https://docs.oracle.com/javase/7/docs/api/java/util/concurrent/ScheduledExecutorService.html

 import static java.util.concurrent.TimeUnit.*;
 class BeeperControl {
   private final ScheduledExecutorService scheduler =
     Executors.newScheduledThreadPool(1);

   public void beepForAnHour() {
     final Runnable beeper = new Runnable() {
       public void run() { System.out.println("beep"); }
     };
     final ScheduledFuture<?> beeperHandle =
       scheduler.scheduleAtFixedRate(beeper, 10, 10, SECONDS);
     scheduler.schedule(new Runnable() {
       public void run() { beeperHandle.cancel(true); }
     }, 60 * 60, SECONDS);
   }
 }

Upvotes: 2

Daniel Alexiuc
Daniel Alexiuc

Reputation: 13240

Ok it looks like it is possible, but there is probably a better approach.

Since a recurring job only takes a Runnable (with a void return type) there is no way to return the result of the task. So the only way to stop the recurring task is to make the task perform a side-effect, e.g. adding a stop message to a queue. Then a separate thread would need to monitor this queue, and it could cancel the job once it sees the message.

Very messy and complicated.

A better alternative is to create a normal (one time) scheduled task. The task itself can then decide whether or not it needs to schedule another task, and can do the scheduling of the next task itself.

Upvotes: 3

Related Questions