H2ONaCl
H2ONaCl

Reputation: 11279

Why is a TimerTask not reusable with a new Timer object?

The Timer (java.util.Timer) doc describes the cancel method as one that affects the Timer and it states that the timer cannot be used after cancellation. So I instantiate a new Timer. Why will it not let me re-use the argument task0 in this example? I'm not even invoking purge which is described as making tasks GC-eligible. Until it might be explained to be otherwise, I claim Timer class should not affect a TimerTask object that is merely an argument to it.

import java.util.Timer;
import java.util.TimerTask;

public class Tester {

    public static void main(String[] args) throws InterruptedException {
        long delay = 3000L;

        Timer timer0 = new Timer();
        Task task0 = new Task();
        timer0.schedule(task0, delay);
        timer0.cancel();

        Timer timer1 = new Timer();
        timer1.schedule(task0, delay); // throws an exception if we use task0

        Thread.sleep(5000);
        timer1.cancel();
    }
}

class Task extends TimerTask {
    Task() {
    }
    @Override
    public void run() {
        System.out.println("task was invoked");
    }
}

Upvotes: 11

Views: 8447

Answers (4)

user13887828
user13887828

Reputation:

Alternatively, you can create a method to return the same task and then schedulde it to the same timer or different timers:

    import java.util.Timer;
    import java.util.TimerTask;

    public class Tester {

        public static void main(String[] args) throws InterruptedException {
            long delay = 3000L;
            Timer timer0 = new Timer();
            timer0.schedule(createTask(), delay);

            Timer timer1 = new Timer();
            timer1.schedule(createTask(), delay); 
            Thread.sleep(5000);
            timer1.cancel();
            
            // purge all canceled tasks from timers
            timer0.purge();
            timer1.purge();
            
        }
    }

    Task createTask() {
        Task task0 = new Task();
        return task0;
    }

    class Task extends TimerTask {
        Task() {
        }
        @Override
        public void run() {
            System.out.println("task was invoked");
            // here do something
            cancel(); // stop it.
        }
    }

Upvotes: 0

Wolfgang Liebich
Wolfgang Liebich

Reputation: 408

This also is a problem if a timer task wants to re-schedule itself in the same Timer - maybe with a different delay this time. This would allow me for example to implement something like an exponential backoff algorithm (retry a task, with exponentially growing delays.

It seems such a timer task with variable delays can be most comfortably implemented by using a ScheduledExecutorService, because this class does not pose such restrictions.

Upvotes: 1

James
James

Reputation: 8586

Take a look:

http://www.docjar.com/html/api/java/util/TimerTask.java.html

http://www.docjar.com/html/api/java/util/Timer.java.html

The TimerTask class is just a thin extension of Runnable which tracks a bit of metadata about scheduling (namely: next execution time). But, if you schedule it on two timers, there's still only one next execution field, so one timer would overwrite the next execution time of the other, which is almost certainly not what you want, so it tracks that it's been scheduled before, and throws an exception in Timer, instead.

If it allowed this, you'd get rather unexpected behavior.

Upvotes: 2

aioobe
aioobe

Reputation: 421040

Allowing this would be error prone, since task0 could still be running when scheduled again by another timer. (Note that cancel() does not terminate the task.)

Note that if task0 is managed by a single Timer, the same task will never be executed concurrently with itself (regardless if it is executed with fixed-delay or with fixed-rate).

If you really want such behavior, the work around would be to let task0 and a task1 wrap a common object:

class Task extends TimerTask {
    Runnable runnable;
    Task(Runnable runnable) {
        this.runnable = runnable;
    }
    @Override
    public void run() {
        runnable.run();
    }
}

And then execute it like this:

// "Wrapped" (and thus shared) by task0 and task1 below.
Runnable runnable = new Runnable() {
    @Override
    public void run() {
        System.out.println("task was invoked");
    }
}



Timer timer0 = new Timer();
Task task0 = new Task(runnable);
timer0.schedule(task0, delay);
timer0.cancel();


Task task1 = new Task(runnable);
Timer timer1 = new Timer();
timer1.schedule(task1, delay); // throws an exception if we use task0

Thread.sleep(5000);
timer1.cancel();

Upvotes: 7

Related Questions