Java计时器

20

我正在尝试使用定时器在应用程序中安排一个重复事件。然而,我希望能够根据用户的输入实时调整事件触发的时间间隔。

例如:

public class HelperTimer extends TimerTask
{
    private Timer timer;
    //Default of 15 second between updates
    private int secondsToDelay = 15;

    public void setPeriod(int seconds)
    {
        this.secondsToDelay = seconds;
        long delay = 1000; // 1 second
        long period = 1000*secondsToDelay; // seconds
        if (timer != null) 
        {
            timer.cancel();
        }
        System.out.println(timer);
        timer = new Timer();
        System.out.println(timer);
        timer.schedule(this, delay, period);
    }
    public int getPeriod()
    {
        return this.secondsToDelay;
    }
}

我创建了一个类的新实例并调用它的set period函数。但是,当我这样做时,我会得到一个非法状态异常。你可以看到其中的System.out.println(timer);,因为我正在进行检查,确实,它们是两个不同的计时器......那么为什么我在尝试在全新的计时器实例上运行调度调用时会得到IllegalStateException呢?!?!?

java.util.Timer@c55e36
java.util.Timer@9664a1
Exception in thread "AWT-EventQueue-0" java.lang.IllegalStateException: Task already scheduled or cancelled
    at java.util.Timer.sched(Unknown Source)
    at java.util.Timer.schedule(Unknown Source)
    at HelperTimer.setPeriod(HelperTimer.java:38)
4个回答

21

你不能像你在这里做的那样重用一个TimerTask。

Timer 的相关部分:

private void sched(TimerTask task, long time, long period) {
    if (time < 0)
        throw new IllegalArgumentException("Illegal execution time.");

    synchronized(queue) {
        if (!thread.newTasksMayBeScheduled)
            throw new IllegalStateException("Timer already cancelled.");

        synchronized(task.lock) {
            //Right here's your problem.
            //  state is package-private, declared in TimerTask
            if (task.state != TimerTask.VIRGIN)
                throw new IllegalStateException(
                    "Task already scheduled or cancelled");
            task.nextExecutionTime = time;
            task.period = period;
            task.state = TimerTask.SCHEDULED;
        }

        queue.add(task);
        if (queue.getMin() == task)
            queue.notify();
    }
}

你需要重构你的代码,这样你就可以创建一个新的TimerTask,而不是重新使用一个。


1
正如凯文所说,你每次都在使用相同的“this”调用“timer.schedule(this, delay, period)”。 TimerTask 不应该被分配给多个不同的计时器。每个 TimerTask 实例只能被安排一次。 - Eddie
1
谢谢,这在文档中完全没有提到,但是一旦我切换到创建新的TimerTasks,一切都按计划进行了。 - Zak
2
日程表规范说明:如果任务已经被预定或取消,定时器已经取消或定时器线程终止,则会抛出IllegalStateException异常。因为该任务已经在早先的定时器中被安排了,所以抛出了异常。 - notnoop
msaeed 关于文档的说法是正确的,尽管它不像应该的那样清晰。此外,虽然我并不是教条主义者,但 Java 的操作系统性质确实有助于追踪这些“不太清楚”的错误。 - Kevin Montrose
我建议这个更好的解决方案(是的,自吹自擂,但我觉得应该为记录而说 :-P):https://dev59.com/3XVD5IYBdhLWcg3wRpeX#32057#32057 - C. K. Young

4

在我的看法中,将TimerTask与其自己的Timer放在一起似乎有些奇怪。这是一个不好的设计。我完全会将这两者分开,将TimerTask实现交给Timer处理,并将所有关于调整时间间隔的逻辑放在另一个提供此功能接口的类中。让那个类实例化Timer和TimerTask并将它们送去完成工作。


0
你可以使用ScheduledExecutorService,它允许你多次调度相同的任务而不使用scheduleAtFixedRate。这里是一个快速的示例:
ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1);
Runnable timerTask = new Runnable() {
    @Override
    public void run() {
        // Do something
        System.out.println("Task run!");
        // Schedule again
        executorService.schedule(this, 15, TimeUnit.SECONDS);
    }
};
// Schedule
executorService.schedule(timerTask, 15, TimeUnit.SECONDS);

0
在这个例子中,将会在延迟4秒后打印出"Executed...."。之后,它将每隔3秒连续打印一次:
import java.util.*;

class TimeSetting {
    public static void main(String[] args) {
        Timer t = new Timer();
        TimerTask time = new TimerTask() {
            public void run() {
                System.out.println("Executed......");
            }
        };
        t.scheduleAtFixedRate(time, 4000, 3000);
        /*
        * The task will be started after 4 secs and 
        * for every 3 seconds the task will be continuously 
        * executed.....
        */
    }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接