JavaFX定期后台任务
我尝试定期在JavaFX应用程序后台线程中运行,这会修改一些GUI属性 我想我知道如何使用JavaFX定期后台任务,javafx,Javafx,我尝试定期在JavaFX应用程序后台线程中运行,这会修改一些GUI属性 我想我知道如何使用javafx.concurrent中的Task和Service类,如果不使用Thread\sleep()方法,我就无法理解如何运行这样的周期性任务。如果我可以使用Executors中的Executor制作方法(Executors.newSingleThreadScheduledExecutor()),那就太好了 我试图每5秒运行一次Runnable,这会重新启动javafx.concurrent.Servi
javafx.concurrent
中的Task
和Service
类,如果不使用Thread\sleep()
方法,我就无法理解如何运行这样的周期性任务。如果我可以使用Executors
中的Executor
制作方法(Executors.newSingleThreadScheduledExecutor()
),那就太好了
我试图每5秒运行一次Runnable
,这会重新启动javafx.concurrent.Service
,但它会立即挂起,因为Service.restart
甚至调用了Service.getState()
因此,最后我使用了Executors.newSingleThreadScheduledExecutor()
,它每5秒启动一次Runnable
,并且Runnable
运行另一个Runnable
,使用:
Platform.runLater(new Runnable() {
//here i can modify GUI properties
}
这看起来很糟糕:(有没有更好的方法使用
任务
或服务
类来完成此任务?您可以使用时间线来完成此任务:
Timeline fiveSecondsWonder = new Timeline(
new KeyFrame(Duration.seconds(5),
new EventHandler<ActionEvent>() {
@Override
public void handle(ActionEvent event) {
System.out.println("this is called every 5 seconds on UI thread");
}
}));
fiveSecondsWonder.setCycleCount(Timeline.INDEFINITE);
fiveSecondsWonder.play();
下面是一个使用Java 8和的解决方案。假设您希望定期重新计算
Label.textProperty()
的值
Label=。。。;
EventStreams.ticks(Duration.ofSeconds(5))//发出周期性的ticks
.supplyCompletionStage(()->getStatusAsync())//在每个刻度上启动后台任务
.await()//准备就绪时发出任务结果
.subscribe(label::setText);//为每个结果执行label.setText()
CompletionStage getStatusAsync(){
返回CompletableFuture.SupplySync(()->getStatusFromNetwork());
}
字符串getStatusFromNetwork(){
// ...
}
与Sergey的解决方案相比,您没有将整个线程用于从网络获取状态,而是使用共享线程池来获取状态。我更喜欢PauseTransition:
PauseTransition wait=新的PauseTransition(持续时间.秒(5));
等等,setOnFinished((e)->{
/*你的方法*/
等等,播放fromstart();
});
等等,play();
您也可以使用。我在使用此选项时注意到,在使用时间线和暂停转换期间,我的应用程序中出现了一些UI冻结,特别是当用户与菜单栏的元素交互时(在JavaFX12上)。使用ScheduledService
这些问题不再发生
class UpdateLabel extends ScheduledService<Void> {
private Label label;
public UpdateLabel(Label label){
this.label = label;
}
@Override
protected Task<Void> createTask(){
return new Task<Void>(){
@Override
protected Void call(){
Platform.runLater(() -> {
/* Modify you GUI properties... */
label.setText(new Random().toString());
});
return null;
}
}
}
}
前言:对于询问如何在JavaFX中执行定期操作、是否应该在后台执行操作的问题,这个问题通常是重复的目标。虽然这个问题已经有了很好的答案,但这个答案试图整合所有给定的信息(以及更多)归纳成单一答案,并解释/显示每种方法之间的差异
这个答案关注JavaSE和JavaFX中可用的API,而不是第三方库,如ReactFX(如中所示)
背景信息:JavaFX和Threads
与大多数主流GUI框架一样,JavaFX是单线程的。这意味着有一个线程专门用于读写UI状态和处理用户生成的事件(例如鼠标事件、按键事件等)。在JavaFX中,该线程称为“JavaFX应用程序线程”,有时简称为“FX线程”,但其他框架可能会称之为其他名称。其他一些名称包括“UI线程”、“事件调度线程”和“主线程”
绝对重要的是,任何连接到屏幕上显示的GUI的内容都只能在JavaFX应用程序线程上访问或操作。JavaFX框架不是线程安全的,使用不同的线程不正确地读取或写入UI状态可能会导致未定义的行为。即使您没有看到任何外部可见的问题是,在没有中断代码的情况下访问线程之间共享的状态
但是,许多GUI对象可以在任何线程上进行操作,只要它们不是“活动的”
节点对象可以在任何线程上构造和修改,只要它们尚未附加到窗口中的场景
(即
[emphasis added])。应用程序必须将节点附加到这样的场景或在JavaFX应用程序线程上修改它们
但其他GUI对象,如窗口
,甚至是节点
的一些子类(例如WebView
),更为严格。例如,从以下文档:
必须在JavaFX应用程序线程上构造和修改窗口对象
如果您不确定GUI对象的线程规则,其文档应该提供所需的信息
由于JavaFX是单线程的,您还必须确保永远不要阻止或垄断FX线程。如果线程不能自由完成其工作,则用户界面永远不会被重新绘制,并且无法处理新的用户生成的事件。不遵守此规则可能会导致臭名昭著的无响应/冻结用户界面,您的用户也不会满意
JavaFX应用程序线程实际上总是错误的
定期任务
至少就本回答而言,有两种不同类型的定期任务:
定期前台“任务”。
- 这可能包括“闪烁”节点或定期在图像之间切换等内容
定期的背景任务。
- 例如,定期检查远程服务器是否有更新,如果有,下载新信息并将其显示给用户
定期前台任务
如果你的周期性任务短而简单,那么使用后台线程就太过分了,只会增加不必要的复杂性。更合适的解决方案是使用API。动画是异步的,但完全在JavaFX应用程序中
Label label = ...;
EventStreams.ticks(Duration.ofSeconds(5)) // emits periodic ticks
.supplyCompletionStage(() -> getStatusAsync()) // starts a background task on each tick
.await() // emits task results, when ready
.subscribe(label::setText); // performs label.setText() for each result
CompletionStage<String> getStatusAsync() {
return CompletableFuture.supplyAsync(() -> getStatusFromNetwork());
}
String getStatusFromNetwork() {
// ...
}
class UpdateLabel extends ScheduledService<Void> {
private Label label;
public UpdateLabel(Label label){
this.label = label;
}
@Override
protected Task<Void> createTask(){
return new Task<Void>(){
@Override
protected Void call(){
Platform.runLater(() -> {
/* Modify you GUI properties... */
label.setText(new Random().toString());
});
return null;
}
}
}
}
class WindowController implements Initializable {
private @FXML Label randomNumber;
@Override
public void initialize(URL u, ResourceBundle res){
var service = new UpdateLabel(randomNumber);
service.setPeriod(Duration.seconds(2)); // The interval between executions.
service.play()
}
}