我今天才开始学习JavaFX,我试图通过制作Snake克隆来了解更多信息,但我遇到了线程问题。我想创建一个更新蛇在屏幕上的位置的线程,但是不能以正常的Runnable线程方式使用它,因为我在该线程中使用JavaFX来更新绘制到的矩形的位置屏幕(我学到了你做不到,而且必须使用Tasks,Services,Platform.runLater等?)我创建线程的类扩展了JavaFX.scene.layout.Pane,我试图使用一个任务更新蛇的位置。我的问题是:任务似乎只运行一次或两次并退出,没有我给出任何中断。
扩展Pane的类的构造函数(Snake类扩展了Group):
public GameFrame(){
this.setPrefSize(800, 600);
Snake snake = new Snake();
this.getChildren().add(snake);
taskThread = new Thread(new Task<Void>() {
protected Void call() throws Exception {
while(!Thread.currentThread().isInterrupted()){
snake.updatePosition();
try{
Thread.sleep(1000);
} catch(InterruptedException e){
break;
}
}
return null;
}
});
taskThread.start();
}
我觉得我实际上并没有意识到这里最好的事情是什么,而我想做的事情可能是hackish。对于我应该做什么,或者我如何解决这个问题,有什么建议吗?
答案 0 :(得分:4)
JavaFX中线程的基本规则(如果你已经理解了一些,我只想完成)请原谅我:
Node
状态的内容都应该在FX应用程序线程上执行为了帮助实现这些目标,JavaFX API提供了Task
类。这有一个返回值的call()
方法;它是Runnable
,因此可以作为Thread
构造函数的参数提供,或传递给Executor
。它还提供有用的回调,保证在FX应用程序线程上执行,例如setOnSucceeded
,setOnFailed
以及更新update...()
等属性的各种progress
方法。 FX应用程序主题上的message
。
但是,Task
类实际上是为一次性任务设计的:例如,考虑需要从数据库中检索数据的应用程序,这可能需要一些时间。这些执行特定操作并返回结果。你的情况有所不同,因为你的线程正在不断执行。
在这种情况下,使用简单的Thread
并使用Platform.runLater(...)
来更新UI可能会更好。 Platform.runLater(...)
获取Runnable
并在FX应用程序主题上执行其run()
方法。
我不清楚为什么你的代码按照你描述的方式行事,但假设方法调用snake.updatePosition()
导致UI的变化,应该在FX应用程序线程上执行。无论如何我会尝试
taskThread = new Thread(new Runnable() {
public void run() {
while(!Thread.currentThread().isInterrupted()){
Platform.runLater(new Runnable() {
@Override
public void run() {
snake.updatePosition();
}
});
try{
Thread.sleep(1000);
} catch(InterruptedException e){
break;
}
}
}
});
如果您使用的是Java 8,那么lambdas取代匿名内部类的效果会更好:
taskThread = new Thread( () -> {
while (! Thread.currentThread().isInterrupted()) {
Platform.runLater( snake::updatePosition );
try {
Thread.sleep(1000);
} catch (InterruptedException exc) {
break ;
}
}
});
JavaFX中用于定期执行某些操作的另一种技术是(ab?)使用动画:
Timeline timeline = new Timeline(new KeyFrame(Duration.seconds(1),
new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent event) {
snake.updatePosition();
}
}
));
timeline.setCycleCount(Animation.INDEFINITE);
timeline.play();
或者,在Java 8中,有点光滑
Timeline timeline = new Timeline(new KeyFrame(Duration.seconds(1),
event -> snake.updatePosition()));
timeline.setCycleCount(Animation.INDEFINITE);
timeline.play();