【问题标题】:Java FX preventing UI from freezingJava FX 防止 UI 冻结
【发布时间】:2020-01-24 16:17:35
【问题描述】:

我希望用户等到我的下载文件的方法调用完成,这可能需要大约 2 到 3 分钟。我为方法调用创建了一个新线程,并在run() 方法中调用它。根据返回值,应该显示一条消息,无论是成功还是错误。因此我不得不使用join()。但这仍然冻结,因为 UI 仍在等待方法调用完成。我该如何克服这个问题? (基本上,我需要使用一个线程让UI不冻结,同时我想根据方法的返回值显示一条消息)

btn.setOnAction(new EventHandler<ActionEvent>() {
   @Override
   public void handle(ActionEvent event) {
      processMsg.setText("Do not close. This may take few minutes...");
      Thread t1 = new Thread(new Runnable(){
         @Override
         public void run() {
            try {
                   ret = /*Method-call-which-takes-long-time*/
                } catch (IOException ex) {
                     Logger.getLogger(JavaFXApplication1.class.getName()).log(Level.SEVERE, null, ex);
                } catch (SQLException ex) {
                     Logger.getLogger(JavaFXApplication1.class.getName()).log(Level.SEVERE, null, ex);
                }
         }
      });
      t1.start();
      try {
           t1.join();
      } catch (InterruptedException ex) {
           Logger.getLogger(JavaFXApplication1.class.getName()).log(Level.SEVERE, null, ex);
      }
      if (ret != null){
         if (ret.equals("TRUE")){
            pane.getChildren().remove(processMsg);
            processMsg.setText("File downloaded at location: "+ chosenDir.getAbsolutePath());
            pane.add(processMsg,0,11,2,1);
         }
         else{
            pane.getChildren().remove(processMsg);
            processMsg.setText(ret);
            pane.add(processMsg,0,11,2,1);
         }
      }
   }
});

【问题讨论】:

标签: java multithreading javafx


【解决方案1】:

根据返回值,应该显示一条消息,无论是成功还是错误。因此我不得不使用join()

这个假设是错误的。有一些方法可以从后台线程更新 GUI。 Platform.runLater 是最灵活的一个,但在这种情况下,我只推荐使用Task,它允许您通过onSucceededonFailed 属性添加处理程序,这些属性在call 逻辑完成时调用,有或没有抛出一个例外:

Task<MyReturnType> task = new Task<MyReturnType>() {

    @Override
    protected MyReturnType call() throws Exception {
        return someLongCalculation(); // may throw an exception
    }

};

task.setOnSucceeded(evt -> {
    // we're on the JavaFX application thread here
    MyReturnType result = task.getValue();
    label.setText(result.toString());

});

task.setOnFailed(evt -> {
    // we're on the JavaFX application thread here
    label.setText("Error: " + task.getException().getMessage());
});
new Thread(task).start(); // alternatively use ExecutorService

Task.updateValueTask.updateMessageTask.updateProgress 允许您返回可以使用 valuemessageprogress 属性观察到的部分结果;这些属性在 JavaFX 应用程序线程上更新。

【讨论】:

    【解决方案2】:

    我建议您查看Oracle Tutorial,了解如何处理 UI 组件之外的业务逻辑。

    此外,关于您的问题,使用 CompletableFuture 对执行此类异步工作有很大帮助。

    具体来说,如果您在CompletableFuture 中使用下载过程然后在之后显示消息,whenComplete() 会有所帮助。

    这是whenComplete()的参考

    【讨论】:

    • 是的,我同意这个答案。一种更标准的方法。
    【解决方案3】:

    您可能知道 JavaFX 渲染是由名为 JavaFX Application Thread 的线程处理的。您可以在 Eclipse 调试模式下查看线程。因此,如果让这个线程等待另一个线程,那么渲染将冻结。这就是这里发生的事情。

    你可以做什么

    1. 单击此按钮时,禁用您不希望用户单击的按钮。
    2. 使用容器轮询结果。这可以是一个全局队列,也可以使用 RxJava BehaviorSubjectSubject 的一些其他合适的实现来优雅地实现。
    3. 当耗时方法完成后,它将使用结果填充队列或在 RxJava 上调用 onNext() Subject
    4. 当队列的侦听器或Subject 获得值时,它会重新启用按钮。

    让我看看我能不能快速整理出一个样本。同时,您可以自行尝试。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-08-12
      • 1970-01-01
      • 1970-01-01
      • 2011-03-12
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多