【问题标题】:JavaFX Running a large amount of countdown timers at once?JavaFX 一次运行大量倒数计时器?
【发布时间】:2018-12-03 04:10:22
【问题描述】:

所以我可以看到几种不同的方法来做我需要的事情,我已经完成了一堆 google/stack 溢出搜索,但找不到我真正想要的东西。我需要运行多个“倒数计时器”。我需要在不同时间同时运行大约 6 个可能最多 10 个倒数计时器。我的主程序上有一个选项卡窗格,其中包含 FXML 并将控制器注入其中。计时器选项卡具有与主程序不同的控制器。

所以我的第一个问题是。由于这个“选项卡”运行在单独的控制器上,但包含在主程序中,它是否运行在单独的应用程序线程上?

这是包含的选项卡 FXML 的示例...

当我按下每个开始按钮时。我可以为每个计时器创建一个TimelineKeyFrame。但是,我真的不认为这是最好的方法。特别是一旦您同时运行多达 10 个时间线,并且绝对不是在与主程序分开的应用程序线程上运行时。

我考虑将每个启动请求发送到 ExecutorServicenewCacheThreadPool 但是我需要能够使用当前剩余时间更新 GUI 上的标签,我知道您不应该使用后台服务执行此操作. Platform.runLater() 也许?

另一个想法是使用java.util.Timer 类中的Timer。但是,当我需要更新 GUI 标签时,我认为这与ExecutorService 存在相同的问题。我也明白Timer 类只创建一个线程并按顺序执行它的任务。所以,这行不通。

或者,我是否应该有一个完整的“CountDown”类,我可以为每个类创建新实例,然后在其中启动新线程。但是,如果我这样做,我如何不断更新 GUI。我仍然需要使用timeline 来轮询 CountDown 类,对吗?所以这会破坏整个事情的目的。

【问题讨论】:

    标签: java javafx timer javafx-8 fxml


    【解决方案1】:

    所以我的第一个问题是。由于这个“选项卡”运行在单独的控制器上,但包含在主程序中,它是否运行在单独的应用程序线程上?

    不,每个 JVM 只能有 一个 JavaFX 应用程序实例,并且每个 JVM 只能有 一个 JavaFX 应用程序线程。

    至于如何更新计时器,可以使用Timeline - 每个计时器一个。 Timeline 不在单独的线程上运行 - 它由负责定期更新 JavaFX GUI 的底层“场景图渲染脉冲”触发。拥有更多的Timeline 实例基本上只是意味着有更多的侦听器订阅了“pulse”事件。

    public class TimerController {
        private final Timeline timer;
    
        private final ObjectProperty<java.time.Duration> timeLeft;
    
        @FXML private Label timeLabel;
    
        public TimerController() {
            timer = new Timeline();
            timer.getKeyFrames().add(new KeyFrame(Duration.seconds(1), ae -> updateTimer()));
            timer.setCycleCount(Timeline.INDEFINITE);
    
            timeLeft = new SimpleObjectProperty<>();
        }
        public void initialize() {
            timeLabel.textProperty().bind(Bindings.createStringBinding(() -> getTimeStringFromDuration(timeLeft.get()), timeLeft));
        }
    
        @FXML
        private void startTimer(ActionEvent ae) {
            timeLeft.set(Duration.ofMinutes(5)); // For example timer of 5 minutes
            timer.playFromStart();
        }
    
        private void updateTimer() {
            timeLeft.set(timeLeft.get().minusSeconds(1));
        }
    
        private static String getTimeStringFromDuration(Duration duration) {
            // Do the conversion here...
        }
    }
    

    当然,您也可以使用Executor 和其他线程方法,前提是您通过Platform.runLater() 更新Label。或者,您可以使用Task

    这是使用后台线程时的一般示例:

    final Duration countdownDuration = Duration.ofSeconds(5);
    Thread timer = new Thread(() -> {
        LocalTime start = LocalTime.now();
        LocalTime current = LocalTime.now();
        LocalTime end = start.plus(countDownDuration);
    
        while (end.isAfter(current)) {
            current = LocalTime.now();
            final Duration elapsed = Duration.between(current, end);
    
            Platform.runLater(() -> timeLeft.set(current)); // As the label is bound to timeLeft, this line must be inside Platform.runLater()
            Thread.sleep(1000);
        }
    });
    

    【讨论】:

    • 天哪!非常感谢你。我想我想通过创建这么多Timelines 它会使一切陷入困境。在上面的示例中,您仅通过绑定更新一个标签。如果我试图独立运行每个计时器,我应该把它变成一个单独的类。但是,如果我这样做,我如何将计时器绑定到 tabController 上的适当标签?我真的刚刚醒来,所以如果这没有意义,请给我。哈哈。
    • 没关系。当我写那条评论时,我的大脑还没有完全运作。我知道如何使这项工作。谢谢!
    • @Kfly 是的,因为你在那里的每个计时器看起来和行为都是一样的,你可以把它变成一个可重用的组件。尝试创建单个计时器,然后在需要更多计时器时创建多个实例会更容易。
    【解决方案2】:

    要添加到 Jai 发布的 good answer,您可以测试不同实现的性能,并通过简单的打印输出了解它们是否使用单独的线程:

    import java.io.IOException;
    import javafx.animation.Animation;
    import javafx.animation.KeyFrame;
    import javafx.animation.PauseTransition;
    import javafx.animation.Timeline;
    import javafx.application.Application;
    import javafx.application.Platform;
    import javafx.concurrent.Task;
    import javafx.geometry.Pos;
    import javafx.scene.Scene;
    import javafx.scene.control.Label;
    import javafx.scene.layout.VBox;
    import javafx.stage.Stage;
    import javafx.util.Duration;
    
    public class TimersTest extends Application {
    
        @Override public void start(Stage stage) throws IOException {
    
            System.out.println("Fx thread id "+ Thread.currentThread().getId());
    
            VBox root = new VBox(new TimeLineCounter(), new PauseTransitionCounter(), new TaskCounter());
            stage.setScene(new Scene(root));
            stage.show();
        }
    
        public static void main(String[] args) { launch(args); }
    }
    
    abstract class Counter extends Label {
    
        protected int count = 0;
        public Counter() {
            setAlignment(Pos.CENTER); setPrefSize(25, 25);
            count();
        }
    
        abstract void count();
    }
    
    class TimeLineCounter extends Counter {
    
        @Override
        void count() {
    
            Timeline timeline = new Timeline();
            timeline.setCycleCount(Animation.INDEFINITE);
            KeyFrame keyFrame = new KeyFrame(
                    Duration.seconds(1),
                    event -> {  setText(String.valueOf(count++) );  }
            );
            timeline.getKeyFrames().add(keyFrame);
            System.out.println("TimeLine thread id "+ Thread.currentThread().getId());
            timeline.play();
        }
    }
    
    class PauseTransitionCounter extends Counter {
    
        @Override
        void count() {
    
            PauseTransition pauseTransition = new PauseTransition(Duration.seconds(1));
            pauseTransition.setOnFinished(event ->{
                setText(String.valueOf(count++) );
                pauseTransition.play();
            });
            System.out.println("PauseTransition thread id "+ Thread.currentThread().getId());
            pauseTransition.play();
        }
    }
    
    class TaskCounter extends Counter {
    
        @Override
        void count() { count(this); }
    
        void count(final Label label) {
    
             Task<Void> counterTask = new Task<>() {
                    @Override
                    protected Void call() throws Exception {
                        try {
                            System.out.println("Task counter thread id "+ Thread.currentThread().getId());
                            while(true){
                                Platform.runLater(() -> label.setText(String.valueOf(count++)));
                                Thread.sleep(1000);
                            }
                        } catch (InterruptedException e) {e.printStackTrace();     }
                        return null;
                    }
                };
    
                Thread th = new Thread(counterTask);   th.setDaemon(true);    th.start();
        }
    }
    

    如预期的那样,打印输出显示 TimelinePauseTransition 在 FX 线程上,而 Task 不在:

    Fx 线程 id 15
    TimeLine 线程 id 15
    PauseTransition 线程 id 15
    任务计数器线程 id 19

    【讨论】:

    • 再次感谢 c0der!我从来没有真正想过这样做。
    【解决方案3】:

    您正在寻找的是 RxJava 及其与 JavaFx 的桥梁,即 RxJavaFx。 导入依赖:

    <dependency>
        <groupId>io.reactivex.rxjava2</groupId>
        <artifactId>rxjavafx</artifactId>
        <version>2.2.2</version>
    </dependency>
    

    然后运行

    import java.util.concurrent.TimeUnit;
    
    import io.reactivex.Observable;
    import io.reactivex.rxjavafx.observables.JavaFxObservable;
    import io.reactivex.rxjavafx.schedulers.JavaFxScheduler;
    import io.reactivex.schedulers.Schedulers;
    import javafx.application.Application;
    import javafx.scene.Scene;
    import javafx.scene.control.Label;
    import javafx.scene.control.ToggleButton;
    import javafx.scene.layout.HBox;
    import javafx.scene.layout.VBox;
    import javafx.stage.Stage;
    
    public class TimersApp extends Application {
    
        public static void main(String[] args) {
            launch(args);
        }
    
        @Override
        public void start(Stage stage) throws Exception {
    
            VBox vBox = new VBox();
            for (int i = 0; i < 4; i++) {
                ToggleButton button = new ToggleButton("Start");
                Label label = new Label("0");
                HBox hBox = new HBox(button, label, new Label("seconds"));
                vBox.getChildren().add(hBox);
    
                JavaFxObservable.valuesOf(button.selectedProperty())
                .switchMap(selected -> {
                    if (selected) {
                        button.setText("Stop");
                        return Observable.interval(1, TimeUnit.SECONDS, Schedulers.computation()).map(next -> ++next);
                    } else {
                        button.setText("Start");
                        return Observable.empty();
                    }
                })
                .map(String::valueOf)
                .observeOn(JavaFxScheduler.platform())
                .subscribe(label::setText);
            }
    
            stage.setScene(new Scene(vBox));
            stage.show();
        }
    }
    

    如果您对此解决方案感兴趣,请告诉我。我会提供一些材料给你学习。

    【讨论】:

      【解决方案4】:

      这是另一种方式,标准 java。根据倒计时运行的时间长短,您可能希望在 GUI 关闭时停止这些执行程序。我也在使用 ScheduledExecutorService 进行多个倒计时。

      import java.time.LocalTime;
      import java.time.format.DateTimeFormatter;
      import java.util.concurrent.Executors;
      import java.util.concurrent.ScheduledExecutorService;
      import java.util.concurrent.TimeUnit;
      import java.util.concurrent.atomic.AtomicInteger;
      
      import javafx.application.Application;
      import javafx.application.Platform;
      import javafx.geometry.Insets;
      import javafx.scene.Scene;
      import javafx.scene.control.Button;
      import javafx.scene.control.Label;
      import javafx.scene.layout.HBox;
      import javafx.stage.Stage;
      
      public class CountDownExecutor extends Application{
      
          private final int i= 15;
          private final DateTimeFormatter HH_MM_SS = DateTimeFormatter.ofPattern("HH:mm:ss");
          private final Label l1=new Label("00:00:00");
          private final Insets insets = new Insets(3,5,3,5);
          private final Button button = new Button("Start");
      
          private ScheduledExecutorService executor=null;
          private AtomicInteger atomicInteger = new AtomicInteger();
      
          public static void main(String[] args) {
              Application.launch(CountDownExecutor.class, args);
          }
      
          @Override
          public void start(Stage stage) {
              HBox hb = new HBox();
              button.setOnMouseClicked(a-> countDown());
              button.setPadding(insets);
              l1.setPadding(insets);
              hb.getChildren().addAll(button,l1);
              Scene scene = new Scene(hb);
              stage.setOnCloseRequest((ev)-> {if(executor!=null) executor.shutdownNow();});
              stage.setScene(scene);
              stage.show();
          }
      
          public void countDown() {
              Platform.runLater( () -> button.setDisable(true));
              atomicInteger.set(i);
              setCountDown(LocalTime.ofSecondOfDay(atomicInteger.get()));
              executor = Executors.newScheduledThreadPool(1);
      
              Runnable r = ()->{
                  int j = atomicInteger.decrementAndGet();
                  if(j<1 ){
                      executor.shutdown();
                      Platform.runLater( () ->{ 
                          button.setDisable(false);
                      });
                      setCountDown(LocalTime.ofSecondOfDay(0));
                  }else {
                      setCountDown(LocalTime.ofSecondOfDay(j));
                  }
              };
              executor.scheduleAtFixedRate(r, 1, 1, TimeUnit.SECONDS);
          }
      
          public void setCountDown(LocalTime lt)  { Platform.runLater(() -> l1.setText(lt.format(HH_MM_SS))); }
      } 
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-08-05
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多