JavaFX一次运行大量的倒数计时器吗?

时间:2018-12-03 04:10:23

标签: java javafx timer javafx-8 fxml

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

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

以下是包含的选项卡FXML的示例...

Countdown timers

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

我考虑过将每个启动请求发送到ExecutorServicenewCacheThreadPool,但是我需要能够使用当前剩余时间更新GUI上的标签,并且我知道您不应该这样做具有后台服务。 Platform.runLater()也许吗?

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

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

我只是一直坚持最好的方法,希望我不会被否决或删除主题,因为我没有包含任何代码,也没有太开放供讨论。

谢谢

4 个答案:

答案 0 :(得分:3)

  

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

不,每个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和其他线程方法,只要您通过Label更新Platform.runLater()。或者,您可以使用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);
    }
});

答案 1 :(得分:1)

要添加到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

答案 2 :(得分:0)

您正在寻找的是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();
    }
}

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

答案 3 :(得分:0)

这是标准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))); }
}