简体   繁体   中英

Java FX preventing UI from freezing

I want the user to wait until my method call to download a file, which might take around 2 to 3 minutes, completes. I created a new thread for the method call and calling it inside run() method. Based on the return value, a message should be displayed, whether successful or error. Hence I had to use join() . But this still freezes since the UI is still waiting for the method call to complete. How can I overcome this issue? (Basically, I need to use a thread for UI to not to freeze, and at the same time, I want to display a message based on the method's return value)

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

Based on the return value, a message should be displayed, whether successful or error. Hence I had to use join() .

This assumption is wrong. There are ways to update the GUI from a background thread. Platform.runLater is the most flexible one, but in this case I simply recommend using Task , which allows you to add handlers via onSucceeded and onFailed properties that are invoked when the call logic completes with or without throwing an exception:

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.updateValue , Task.updateMessage and Task.updateProgress allow you to communicate partial results back that can be observed using the value , message and progress properties; those properties are updated on the JavaFX application thread.

I advise you to check this Oracle Tutorial on how to handle business logic outside of UI component.

Also, regarding your question using CompletableFuture would be of a great help to do such asynchronous work.

To be specific, whenComplete() would help if you use the download process in a CompletableFuture then display the message afterwards.

Here is a reference for whenComplete()

You may be aware JavaFX rendering is handled by the thread named JavaFX Application Thread. You can see the threads in Eclipse Debug mode. So, if this thread is made to wait for another, then rendering will freeze. That is what is happening here.

What you may do

  1. When this button is clicked, disable the buttons that you don't want the user to click.
  2. Use a container to poll for the results. This can be a global queue or elegantly implemented using RxJava BehaviorSubject or some other suitable implementation of Subject .
  3. When the time consuming method has completed, it will populate the queue with the result or call onNext() on the RxJava Subject .
  4. When the listener of the queue or the Subject gets the value, it reenables the buttons.

Let me see if I can quickly put together a sample. In the meantime, you can try on your own.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM