JavaFX任务结束和JavaFX线程
我今天才开始学习JavaFX,我试图通过制作一个Snake克隆来了解更多信息,但我遇到了线程问题。 我想创建一个更新蛇在屏幕上的位置的线程,但是不能以正常的Runnable线程方式使用它,因为我在该线程中使用JavaFX来更新绘制到屏幕的矩形位置(我学到了你不能这样做,而且必须改为使用Tasks,Services,Platform.runLater等?)我创建线程的类扩展了JavaFX.scene.layout.Pane,我试图用一个任务来更新蛇的位置。 我的问题是:任务似乎只运行一次或两次并退出,没有我给出任何中断。
扩展Pane的类的构造函数(The Snake类扩展Group):
public GameFrame(){ this.setPrefSize(800, 600); Snake snake = new Snake(); this.getChildren().add(snake); taskThread = new Thread(new Task() { protected Void call() throws Exception { while(!Thread.currentThread().isInterrupted()){ snake.updatePosition(); try{ Thread.sleep(1000); } catch(InterruptedException e){ break; } } return null; } }); taskThread.start(); }
我觉得我实际上并没有意识到这里最好的事情是什么,我想要做的可能是hackish。 对于我应该做什么,或者我如何解决这个问题,有什么建议吗?
JavaFX中线程的基本规则(如果你已经理解了一些,我只想完成),请原谅我:
- 任何阻止执行(或需要很长时间才能执行)的东西都应该在后台线程上运行 – 而不是在FX Application Thread上运行
- 任何改变作为场景图一部分的
Node
状态的东西都应该在FX应用程序线程上执行
为了帮助实现这些目标,JavaFX API提供了一个Task
类。 这有一个返回值的call()
方法; 它是一个Runnable
因此可以作为Thread
构造函数的参数提供,或者传递给Executor
。 它还提供有用的回调,保证在FX应用程序线程上执行,例如setOnSucceeded
, setOnFailed
和各种update...()
方法,这些方法更新FX应用程序线程上的progress
和message
等属性。
但是, Task
类实际上是为一次性任务而设计的:例如,考虑需要从数据库检索数据的应用程序,这可能需要一些时间。 这些执行特定操作并返回结果。 您的情况有所不同,因为您的线程正在连续执行。
在这种情况下,最好使用一个简单的Thread
并使用Platform.runLater(...)
来更新UI。 Platform.runLater(...)
获取Runnable
并在FX Application Thread上执行其run()
方法。
我不清楚为什么你的代码表现得像你描述的那样,但假设方法调用snake.updatePosition()
导致UI的变化,应该在FX Application Thread上执行。 无论如何我会尝试
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() { @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();