什么是在Javafx应用中阻止我的UI线程



我有一个简单的javafx应用程序,该应用程序具有两个小圆圈,应该每0.5 s更改其位置。后来,应该成为行星模拟。目前,我的空间对象的位置会在单独的线程中发生变化,该线程按按下按钮"启动模拟"。同时,我希望我的圆圈(代表行星)一次又一次地绘制始终使用存储在SpaceObject对象中的当前位置。当我将重新抽签限制为三次(而不是通过while ( true ) {的无限量,这就是我实际想要的)时,我会发现GUI在循环运行时没有更新。但是循环完成后,圆圈移至新位置,而后台的计算线程仍在运行。为什么我的GUI线程在while ( i < 3 ) {的时间内被阻止,如何通过圆圈的当前位置同时更新GUI?这是我的代码:

main.java

package plantenbahnen;
import javafx.application.Application;
import javafx.fxml.FXMLLoader;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.stage.Stage;
public class Main extends Application {
    @Override
    public void start(Stage stage) throws Exception {
        Parent root = FXMLLoader.load(getClass().getResource("FXMLDocument.fxml"));
        Scene scene = new Scene(root);
        stage.setScene(scene);
        stage.show();
    }
    public static void main(String[] args) {
        launch(args);
    }
}

controller.java

package plantenbahnen;
import java.net.URL;
import java.util.ArrayList;
import java.util.ResourceBundle;
import javafx.application.Platform;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.Initializable;
import javafx.scene.layout.Pane;
public class Controller implements Initializable {
    @FXML private Pane paneDraw;
    @FXML private Pane paneControls;
    private ArrayList<SpaceObject> universe = new ArrayList<>();
    private Thread calcThread;
    @Override
    public void initialize(URL url, ResourceBundle rb) {
        SpaceObject sun = new SpaceObject("sun", 600, 600);
        universe.add(sun);
        SpaceObject earth = new SpaceObject("earth", 450, 450);
        universe.add(earth);
        MyCalculations myCalc = new MyCalculations(universe);
        calcThread = new Thread(myCalc);
        Draw.drawPlanets(universe, paneDraw);
    }    
    @FXML private void buttonStartSimulation(ActionEvent event) throws InterruptedException {    
        calcThread.start();
        Platform.runLater(new Runnable() {
            @Override public void run() {
                int i = 0;
                //while ( true ) {   // this line is what I want
                while ( i < 3 ) {
                    Draw.drawPlanets(universe, paneDraw);
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        System.out.println(e);
                    }
                    i++;
                }
            }
        });
    }    
}

mycalculations.java

package plantenbahnen;
import java.util.ArrayList;
public class MyCalculations implements Runnable {
    ArrayList<SpaceObject> universe;
    public MyCalculations (ArrayList<SpaceObject> universe) {
        this.universe = universe;
    }
    @Override
    public void run(){
        double toAdd = 100.0;
        while ( true ) {
            for (SpaceObject so: universe) {
                so.setx(so.getx() + toAdd);
                so.sety(so.gety() + toAdd);
            }
            if ( toAdd > 0.0 ) {
                toAdd = -300.0;
            } else {
                toAdd = 300.0;
            }
        }
    }
}

draw.java

package plantenbahnen;
import java.util.ArrayList;
import javafx.scene.Node;
import javafx.scene.layout.Pane;
public class Draw {
    public static void drawPlanets(ArrayList<SpaceObject> universe, Pane pane) {
        for (Node child: pane.getChildren()) {
            System.out.println(child);
        }
        // Clear objects first
        for (SpaceObject so: universe) {
            if ( pane.getChildren().contains(so) ) {
                pane.getChildren().remove(so);
                System.out.println("Removing ... " + so.getName());
            }
        }
        double paneHalfWidth = pane.getPrefWidth() / 2.0;
        double paneHalfHeight = pane.getPrefHeight() / 2.0;
        double scaleFactor = 0.1;
        for (SpaceObject so: universe) {
            so.setCenterX(so.getx() * scaleFactor + paneHalfWidth);
            so.setCenterY(so.gety() * scaleFactor + paneHalfHeight);
            System.out.println("x=" + so.getCenterX() + "   y=" + so.getCenterY());
            so.setRadius(2);
            //so.setColour(Color.BLACK);
            pane.getChildren().add(so);
        }
    }
}

spaceObject.java

package plantenbahnen;
import javafx.scene.shape.Circle;
public class SpaceObject extends Circle {
    private double x,y;
    private String name;
    SpaceObject(String name, double x, double y) {
        this.x = x;
        this.y = y;
        this.name = name;
    }
    public double getx(){
        return this.x;
    }
    public void setx(double value){
        this.x=value;
    }
    public double gety(){
        return this.y;
    }
    public void sety(double value){
        this.y=value;
    }
    public void setName(String name) {
        this.name = name;
    }
    public String getName() {
        return this.name;
    }
}

fxmldocument.fxml

<?xml version="1.0" encoding="UTF-8"?>
<?import java.lang.*?>
<?import java.util.*?>
<?import javafx.scene.*?>
<?import javafx.scene.control.*?>
<?import javafx.scene.layout.*?>
<AnchorPane fx:id="AnchorPane" prefHeight="700.0" prefWidth="800.0" xmlns="http://javafx.com/javafx/8" xmlns:fx="http://javafx.com/fxml/1" fx:controller="plantenbahnen.Controller">
   <children>
      <Pane fx:id="paneDraw" prefHeight="700.0" prefWidth="800.0">
         <children>
            <Pane fx:id="paneControls" prefHeight="66.0" prefWidth="174.0">
               <children>
                  <Button fx:id="buttonStartSimulation" layoutX="26.0" layoutY="21.0" mnemonicParsing="false" onAction="#buttonStartSimulation" text="Start simulation" />
               </children>
            </Pane>
         </children></Pane>
   </children>
</AnchorPane>

预先感谢您的帮助。

尝试这样的东西:

 @FXML private void buttonStartSimulation(ActionEvent event) throws InterruptedException {    
    calcThread.start();
    Thread updaterThread = new Thread( () -> {
        @Override public void run () {
            int i = 0;
            while ( true ) {   // this line is what I want
                Platform.runLater( () -> Draw.drawPlanets(universe, paneDraw) );
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    System.out.println(e);
                }
                i++;
            }
        }
    }
    updaterThread.setDaemon ( true );
    updaterThread.start();
}

您想确保所有呼叫到platform.runlater()很短,不涉及睡眠,快速返回并进行最少的计算 - 所有这些呼叫都必须"在"之间"中的"其他"更新中到UI,例如调整窗口大小,管理按钮按下等等。

顺便说一句 - 我不确定您是否需要" CalcThread"one_answers" UpdaterThread"。我怀疑它们应该是一个线程。但这是一个很好的概念证明。

感谢大家的帮助。我最终使用了Timeline。我要更改的只是控制器,看起来像这样(仅显示相关代码):

public class Controller implements Initializable {
    // ...
    private Thread calcThread;
    Timeline timeline = new Timeline(new KeyFrame(Duration.seconds(0.5), new EventHandler<ActionEvent>() {
        @Override
        public void handle(ActionEvent event) {
            Draw.drawPlanets(universe, paneDraw);
        }
    }));
    @Override
    public void initialize(URL url, ResourceBundle rb) {
        // ...
        MyCalculations myCalc = new MyCalculations(universe);
        calcThread = new Thread(myCalc);
        // ...
    }    
    @FXML private void buttonStartSimulation(ActionEvent event) throws InterruptedException {    
        calcThread.start();
        timeline.setCycleCount(Timeline.INDEFINITE);
        timeline.play();
    }    
}