【问题标题】:How do I wait for a java timer to finish如何等待 Java 计时器完成
【发布时间】:2011-12-24 12:44:12
【问题描述】:

我有一个 java swing GUI,我试图在其中实现一个倒数计时器。我有一系列任务,每个任务都需要在 for 循环中按顺序运行。我已经能够成功地使用摆动计时器来执行倒数计时器,但是一旦我尝试将其放入循环中,我就无法在不冻结 GUI 的情况下更新它。

这是我目前工作的 SSCCE。基本问题是此示例并行运行三个 30 秒计时器。我想要做的是依次运行三个 30 秒的计时器,或者换句话说,它应该等待一个计时器完成,然后再开始下一个计时器。我所做的一切尝试等待计时器完成都会导致 GUI 冻结。

import java.awt.*;
import java.awt.event.*;
import java.util.List;
import java.util.concurrent.TimeUnit;
import javax.security.auth.callback.Callback;
import javax.swing.*;

public class SwingTester extends JFrame {
  public SwingTester() {
    JPanel panel = new JPanel();
    getContentPane().add(panel);
    panel.setLayout(new GridLayout(1,3));
    setDefaultCloseOperation(EXIT_ON_CLOSE);
    // Add the components
    jLblTimer = new JLabel("00:30");
    jLblSet = new JLabel("Set #1");
    jBtnStart = new JButton("Start!");
    jBtnStart.addActionListener(new ActionListener() {
        public void actionPerformed(ActionEvent evt) {jBtnStartActionPerformed(evt);}
    });
    // Add the labels
    panel.add(jLblTimer); panel.add(jLblSet); panel.add(jBtnStart);
    pack();
  }
  private void jBtnStartActionPerformed(ActionEvent evt) {
    for (int ss=0; ss<3; ss++) {
        // Change the set name
        jLblSet.setText("Set #" + Integer.toString(ss+1));
        // Setup the counter to begin the countdown
        counter = 30;
        timer = new Timer(1000, startCycle());
        timer.start();
        // I want to wait here until the timer is done, nothing I've done
        // works without freezing the GUI.
    }
  }
  public static void main(String args[]) {
    java.awt.EventQueue.invokeLater(new Runnable() {
      public void run() {
        new SwingTester().setVisible(true);
      }
    });
  }
  private Action startCycle() {
    return new AbstractAction() {
        @Override
        public void actionPerformed(ActionEvent e) {
            new ClockTask(null).execute();
        }
    };
  }
  class ClockTask extends SwingWorker<Void, String> {
  private Callback callback;
  public ClockTask(Callback callback) {
    this.callback = callback;
  }
  @Override
  protected Void doInBackground() throws Exception {
    if (counter >= 0) {
      int millis = counter*1000;
      String time = String.format("%02d:%02d", TimeUnit.MILLISECONDS.toMinutes(millis),
        TimeUnit.MILLISECONDS.toSeconds(millis) -
        TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(millis))
        );
      publish(time); // call publish which will call process() method in EDT
      counter--;
    } else {
      // this.callback.call();
      this.callback.notify();
      timer.stop();
    }
    return(null);
  }
  @Override
  protected void process(List<String> times) {
    String lastTime = times.get(times.size()-1);
    jLblTimer.setText(lastTime); // just update ui with lastTimer, ohters are ignorable 
  }
  @Override
  protected void done() {
    super.done();
  }

  // private interface Callback {
  //   void call();
  // }
  }
  private javax.swing.JLabel jLblTimer;
  private javax.swing.JLabel jLblSet;
  private javax.swing.JButton jBtnStart;
  private javax.swing.Timer timer;
  private int counter;

}

我也尝试过使用 sleep 功能来等待倒计时结束,但这会阻止 GUI 更新。 任何建议都非常受欢迎。您可能从我的代码中可以看出我不是程序员。

【问题讨论】:

  • “这是我的代码示例” 为了尽快获得更好的帮助,请发布SSCCE
  • @AndrewThompson 感谢您的建议。我犯了错误,只是有一个 SE(简短示例)。我更新了我的代码,使其成为 SSCCE。
  • 运行时错误的 SSCCE 在编译时不会有 16 errors。 :(
  • 再次感谢@AndrewThompson。我忘了添加最后 5 行代码。它现在使用 javac 1.7.0_02 编译对我来说没问题
  • 这里显示了一个(奇怪的)倒计时,在以后的运行中似乎变得更快。 应该做什么?

标签: java multithreading swing timer


【解决方案1】:

以下是您需要如何更改 SwingWorker

class ClockTask extends SwingWorker<Void, String> {

    private Callback callback;

    public ClockTask(Callback callback) {
        this.callback = callback;
    }

    @Override
    protected Void doInBackground() throws Exception {
        if (counter >= 0) {
            int millis = counter*1000;
            String time = String.format("%02d:%02d", 
                TimeUnit.MILLISECONDS.toMinutes(millis),
                TimeUnit.MILLISECONDS.toSeconds(millis) - 
                TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(millis))
            );

            //jLblTimer.setText(time); // dont update UI in backgroudn thread
            publish(time); // call publish which will call process() method in EDT

            counter--;
        } else {
            this.callback.call();
        }
        return(null);
    }

    /*
     * this  method will be called in EDT and will not freeze your UI ;)
     */
    @Override
    protected void process(List<String> times) {
        String lastTime = times.get(times.size()-1);
        jLblTimer.setText(lastTime); // just update ui with lastTimer, ohters are ignorable 
    }

    @Override
    protected void done() {
        super.done();
    }

    private interface Callback {
        void call();
    }

}

你应该多花点时间在SwingWorker的JavaDoc上

【讨论】:

  • @Kowser 我找不到太多关于使用“私人回调回调”的信息;我已经编辑了我的代码以合并您的更改,谢谢。但是,它仍然通过 for 循环不停地运行,然后我似乎有 3 个计时器同时运行。
  • 我试图为您制定一个改进的解决方案,但是从您的代码和最后的 cmets (it still runs through the for-loops without stopping and I then appear to have 3 timers running simultaneously) 很难理解您实际上想要做什么。
  • @Kowser,再次感谢您抽出时间帮助我。我要做的是在 jBtnStartActionPerformed() 函数中运行 3 次 for 循环。每次通过循环时,倒数计时器都应该启动,然后它应该等到它完成才能开始下一个倒数计时器。现在它没有等到计时器结束,我似乎无法弄清楚如何在不冻结 GUI 的情况下做到这一点。
  • 嗨,我试图调查代码,我尝试编译它,它至少出现了 10 个错误,但无法解决这些错误:(
  • 我不是要求完整的代码,但至少是可以编译的,或者至少是概念性的。也许你可以把整个班级
【解决方案2】:

有几种方法可以做到这一点。通常你可以创建一个对象,将它传递给后台工作,后台工作完成后,它使用对象告诉你结果,让你做剩下的工作。

private class ClockTask extends SwingWorker<Void, Void> {

    private Callback _callback;

    public ClockTask(Callback callback) {
        this._callback = callback;
    }

    @Override
    protected Void doInBackground() throws Exception {
        if (counter >= 0) {
            int millis = counter*1000;
            String time = String.format("%02d:%02d", 
                TimeUnit.MILLISECONDS.toMinutes(millis),
                TimeUnit.MILLISECONDS.toSeconds(millis) - 
                TimeUnit.MINUTES.toSeconds(TimeUnit.MILLISECONDS.toMinutes(millis))
            );
            jLblTimer.setText(time);
            counter--;
        } else {
            this._callback.notify();
        }
        return(null);
    }
}

【讨论】:

  • -1 用于在后台线程中更新标签(假设 jlbTimer 是一个 JLabel):SwingWorker 的重点是干净地分离线程并管理与后台的通信 --> EDT跨度>
  • @jeffrey-zhao,这可能是我的错误。我没有发布合适的 SSCCE 供您查看。我已经更新了我的代码,使其成为 SSCCE,我仍然欢迎任何建议。
猜你喜欢
  • 2010-11-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-06-09
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多