JavaFX - Control and Concurrency

2019-02-19 17:46发布

I have a sample Hello World JavaFx. I am using Eclipse and eFxclipse plugin. My Eclipse is kepler which is Eclipse 4.3.2 version and Java servion is Jdk1.7-045.

What I try to add is very little concurrency codes, I just want to update button text in the example. Could this backend task interact with upfront UI control, for example button, scene? If not, how could I make tailored backend task, then interact with UI control?

Thanks in advance

package com.juhani.fx.exer;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import javafx.application.Application;
import javafx.concurrent.Task;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;


public class HelloWorld extends Application{

    private static final short COREPOOLSIZE=2;
    private static final short MAXIMUMPOOLSIZE=2; 
    private static final int WORKQUEUECAPACITY=100;
    private static Logger log = LogManager.getLogger(
            HelloWorld.class.getName());

    private ExecutorService executors = new             ThreadPoolExecutor(COREPOOLSIZE,MAXIMUMPOOLSIZE,20,TimeUnit.MINUTES,new ArrayBlockingQueue<Runnable>(WORKQUEUECAPACITY));



    public static void main(String[] args) {
       LogMessage logMessage = new LogMessage("BEGIN",1.0,1,"HELLOWORLD");
       log.trace(logMessage.toString());
       launch(args);        

   }

   @Override
   public void start(final Stage primaryStage) throws InterruptedException {
      primaryStage.setTitle("Hello World!");
      final Button btn = new Button();
      btn.setText("Say 'Hello World'");
      btn.setOnAction(new EventHandler<ActionEvent>() {
          @Override
          public void handle(ActionEvent event) {
            System.out.println("Hello World!");
         }
       });

      final StackPane root = new StackPane();
      root.getChildren().add(btn);
      final Scene scene= new Scene(root,300,250);
      primaryStage.setScene(scene);
      primaryStage.show();

      Task<Boolean> task = new Task<Boolean>() {
         @Override 
         public Boolean call() {    
            for(int i=0;i<20;i++){
               btn.setText("First row\nSecond row "+i);
               primaryStage.show();
               try {
                  Thread.sleep(1000);
               } catch (InterruptedException e) {
                  log.error(new LogMessage("entering interruption",1.0,2,"exception").toString());                       
               }
            }
            return new Boolean(true);               
          }        
       };           
       executors.submit(task);      
    }   
}

2条回答
我欲成王,谁敢阻挡
2楼-- · 2019-02-19 17:59

This answer specially talks about the use of Platform.runLater. If you are using Task, you are better off updating the UI using the method it provides as stated in kleopatra's answer.


For updating the UI, you have to be on the Javafx thread.

Once you are on any other thread, use Platform.runLater() to update those data back to Javafx UI. A working example can be found below

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

import javafx.application.Application;
import javafx.application.Platform;
import javafx.concurrent.Task;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.layout.StackPane;
import javafx.stage.Stage;


public class HelloWorld extends Application {

    private static final short COREPOOLSIZE = 2;
    private static final short MAXIMUMPOOLSIZE = 2; 
    private static final int WORKQUEUECAPACITY = 100;

    private ExecutorService executors = new 
       ThreadPoolExecutor(COREPOOLSIZE, MAXIMUMPOOLSIZE, 20, TimeUnit.MINUTES,
                          new ArrayBlockingQueue<Runnable>(WORKQUEUECAPACITY));

    public static void main(String[] args) {
        launch(args);        
    }

    @Override
    public void start(final Stage primaryStage) throws InterruptedException {
        primaryStage.setTitle("Hello World!");
        final Button btn = new Button();
        btn.setText("Say 'Hello World'");
        btn.setOnAction(new EventHandler<ActionEvent>() {
            @Override
            public void handle(ActionEvent event) {
                System.out.println("Hello World!");
            }
        });

        final StackPane root = new StackPane();
        root.getChildren().add(btn);
        final Scene scene = new Scene(root, 300, 250);
        primaryStage.setScene(scene);
        primaryStage.show();

        Task<Boolean> task = new Task<Boolean>() {
            @Override 
            public Boolean call() {    
                final AtomicInteger i = new AtomicInteger(0);
                for( ; i.get() < 20; i.incrementAndGet()) {
                    Platform.runLater(new Runnable() {
                        @Override
                        public void run() {
                            btn.setText("First row\nSecond row " + i);                      
                        }
                    });

                try {
                  Thread.sleep(1000);
                }
                catch (InterruptedException e) {}
            }
            return Boolean.valueOf(true);               
          }        
       };           
       executors.submit(task);      
    }   
}

For more information you can go through the links provided here

查看更多
该账号已被封号
3楼-- · 2019-02-19 18:18

A Task is designed to interact with the ui on the fx-application thread, to take advantage of that support you should use it as designed :-)

As a general rule, you must not access ui in the call method [*] of the Task. Instead, update one of its properties (message, progress ...) and bind that property to your ui. Sample code:

Task<Boolean> taskWithBinding = new Task<Boolean>() {
    @Override 
    public Boolean call() {    
        final AtomicInteger i = new AtomicInteger(0);
        for( ; i.get() < 20; i.incrementAndGet()) {
            updateMessage("First row\nSecond row " + i);                      
            try {
                Thread.sleep(1000);
            }
            catch (InterruptedException e) {
                return Boolean.FALSE;
            }
        }
        return Boolean.TRUE;               
    }        
};           
btn.textProperty().bind(taskWithBinding.messageProperty());

[*] The one exception is outlined (wrap the access into an runLater) in the other answer. Doing so is technically correct - but then you are by-passing a Task's abilities and could use an arbitrary Runnable ...

查看更多
登录 后发表回答