Stop a periodic task from within the task itself running in a ScheduledExecutorService
Asked Answered
P

5

22

Is there a nice way to stop the repetition of task from within the task itself when running in a ScheduledExecutorService?

Lets say, I have the following task:

Future<?> f = scheduledExecutor.scheduleAtFixedRate(new Runnable() {
    int count = 0;
    public void run() {
       System.out.println(count++);
       if (count == 10) {
           // ??? cancel self
       }
    }
}, 1, 1, TimeUnit.SECONDS);

From outside, it is easy to cancel via f.cancel(), but how can I stop the repetition at the specified place? (Passing the Future through an AtomicReference is not safe, because there is a potential window when the scheduleAtFixedRate returns f late and the variable is set late too, and the task itself might already run, seeing a null in the reference.)

Pizzeria answered 5/2, 2011 at 21:34 Comment(2)
Why not throw a StopIteration exception which you can catch to remove the call from the executor from the outside? As far as I know Callable's are unaware of whatever executor they're added to.Tiertza
the simplest and rather ugly way is throwing an exception :)Guardianship
W
14

When a repeating task throws an Exception or Error, it is placed in the Future and the task is not repeated again. You can throw a RuntimeException or Error of your choice.

Waive answered 5/2, 2011 at 21:47 Comment(4)
Yes, that is an option. I hope for a more elegant solution.Pizzeria
This is the best way to do it. If you want to make it "pretty" declare a class that extends RuntimeException that properly describes the purpose such as TaskComplete.Jello
@kd304, if you think: that's the very reason the exceptions are created, to signal an 'exceptional' state, I didn't see Peter's answer when I dropped the comment. This is a food (edit: good, darn misplaced fingers) answer :)Guardianship
Won't doing this result in a memory leak in a long running process since the reference to the runnable will still be kept by the scheduledExecutor?Maroon
C
5

Instead of using an anonymous inner class you can use a named class which can then have a property for the Future object you get from the Executor when you schedule a task.

abstract class FutureRunnable implements Runnable {

    private Future<?> future;

    /* Getter and Setter for future */

}

When you schedule a task you can then pass the Future to the Runnable.

FutureRunnable runnable = new FutureRunnable() {

    public void run() {
        if (/* abort condition */)
            getFuture().cancel(false);
    }

};
Future<?> future = executor.scheduleAtFixedRate(runnable, ...);
runnable.setFuture(future);

Maybe you will have to make sure, that the task is not executed before the Future has been set, because otherwise you will get a NullPointerException.

Clarendon answered 5/2, 2011 at 23:59 Comment(1)
a NPE will effectively cancel the task just as fine :)Guardianship
U
2

It seems like bad design for the Runnable to know anything about the executor it is running in, or to throw an error if reaching 10 is not an error state is a hack.

Can you do the loop to 10 outside of the scheduling and execution? This may require using a non-scheduling executor as you'd be scheduling them manually yourself.

Undercoating answered 5/2, 2011 at 22:3 Comment(0)
G
2

Here is another way, that's even Thread safe;

    final Future<?>[] f = {null};
    f[0]=  scheduledExecutor.scheduleAtFixedRate(new Runnable() {
        int count = 0;
        public void run() {

            System.out.println(count++);
            if (count == 10) {
                Future<?> future;
                while(null==(future = f[0])) Thread.yield();//prevent exceptionally bad thread scheduling 
                future.cancel(false);
                return;
                //cancel self
            }
        }
    }, 1, 1, TimeUnit.SECONDS);
Guardianship answered 6/2, 2011 at 0:24 Comment(0)
E
1

Just saw this now... because I wanted to do the same thing... here is my solution, I suspect this is threadsafe.

First create a container for the Future:

public static class Cancel {
    private ScheduledFuture<?> future;

    public synchronized void setFuture(ScheduledFuture<?> future) {
        this.future = future;
    }

    public synchronized void stop() {
        LOG.debug("cancelling {}", future);
        future.cancel(false);
    }
}

And then the future code:

    final Cancel controller = new Cancel();

    synchronized (controller) {
        ScheduledFuture<?> future = scheduler.scheduleWithFixedDelay(() -> {
            if (<CONTINUE RUNNING CONDITION) {

            } else {
                // STOP SCHEDULABLE FUTURE
                controller.stop();
            }
        }, startTime, timeBetweenVisbilityChecks);
        controller.setFuture(future);
    }
}

So notice how the stop will not be callable until the future has been created and the future has been set on the controller.

Bear in mind that the Runnable is the anomymous inner class and this will get run in a different thread altogether.

Encephaloma answered 30/4, 2015 at 10:56 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.