JavaFX定期后台任务

JavaFX定期后台任务,javafx,Javafx,我尝试定期在JavaFX应用程序后台线程中运行,这会修改一些GUI属性 我想我知道如何使用javafx.concurrent中的Task和Service类,如果不使用Thread\sleep()方法,我就无法理解如何运行这样的周期性任务。如果我可以使用Executors中的Executor制作方法(Executors.newSingleThreadScheduledExecutor()),那就太好了 我试图每5秒运行一次Runnable,这会重新启动javafx.concurrent.Servi

我尝试定期在JavaFX应用程序后台线程中运行,这会修改一些GUI属性

我想我知道如何使用
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();

您可以使用该任务的时间线:

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的解决方案相比,您没有将整个线程用于从网络获取状态,而是使用共享线程池来获取状态。

这里有一个使用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();

我更喜欢暂停转换:

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;
         }
      }
   }
}
您也可以使用。我在使用此选项时注意到,在使用
时间线
暂停转换
期间,我的应用程序中出现了一些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
),都是
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()
   }
}