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();

下面是一个使用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()
       }
    }