romi
romi

Reputation: 171

Concurrent task updating complex object JavaFX - swingworker equivalent?

I want to run a task in background updating intermediate results in the view.I am trying to implement MVC JavaFX application. The task is defined in the Model. I want to send to the main threath partial results in order to show them in the View. I use updateValue() to do so. Also, I define object property and a listener in the controller.

My problem: The method changed() from the listener, is not being fired each time that updateValue() is executed in the Task. Why? How can I force it to do this?.

I have not found much complex examples.

What I have so far:

Model.cpp

ComplexObject  _complexO;
public Task<ComplexObject> getModelTask() {
      return new Task<ComplexObject>() {
           @Override
           protected ComplexObject call() throws Exception {

            int    numberOfFiles = 0;
            boolean filesToRead = true;
            while (filesToRead){
                  // ....
              _complexO = new ComplexObject();
              try{
                   //..
                   if(f.exists()){
                       _complexO.initialize();
                       numberOfScans ++;

                     }
                    else{
                       _complexO.initializeToNull();
                     }

                  String stringNumber = Converter.toString(numberOfFiles);
                  updateMessage(stringNumber);                        
                  updateValue(_complexO );
              } catch(Exception ex){
                   ex.printStackTrace();
                   _complexO = null;
                   return _complexO;
              }
             filesToRead = areThereFilesToRead();
             }
             return _complexO;        
        }
   };
}

Controller.cpp

...

Task<  ComplexObject> task = _model.getModelTask();
_AJavaFXTextField.textProperty().bind(task.messageProperty());
_AJavaFXTextField.textProperty().addListener(new ChangeListener<String>() {

   @Override
   public void changed(ObservableValue observable, String oldValue, String newValue) {  
           System.out.println("Success with messageProperty!!" + newValue);
      }       
     });

SimpleObjectProperty<ComplexObject> complexObjectProperty = new SimpleObjectProperty<>();
complexObjectProperty.bind(task.valueProperty());
complexObjectProperty.addListener(new ChangeListener<ComplexObject>(){

   @Override
   public void changed(ObservableValue<? extends ComplexObject> observable, ComplexObject oldValue, ComplexObject newValue) {
      if(newValue.data == null ) {
        System.out.println("value is new!!! " + scansNumber);
      }
      else if(newValue.isValid()){
         System.out.println("I want to plot newValue data here");

       }
      }         
     });

   Thread th=  new Thread(task);
   System.out.println("call TASK");
   th.start();
}

My questions/conclusions here:

  1. How to force to all times that I execute in the task updateValue() to really execute the listener - so execute the code where I want to plot data.

  2. Why it is more times fire the bind for the messageProperty than the valueProperty? - it should be the same number of times.

  3. Why I find that the code of the listener is fired more times when debug mode than normal execution?

  4. Any recomendation of good sources about this topic (from a complex point of view) would be great.

  5. I am looking from something in JavaFX to replace SwingWorker.

  6. What I really whant at the end: To return a list of complexObjects from the task, and ideally, updateValue() would send the objects one per one (partial results)

I have followed: https://docs.oracle.com/javase/8/javafx/api/javafx/concurrent/Task.html

Thanks very much for any contribuction

Upvotes: 0

Views: 606

Answers (1)

fabian
fabian

Reputation: 82451

Task only guaranties that a value passes to updateValue or a value passed later will be set to the value property. This is done to increase performance of the application thread by limiting the number of changes the listeners are notified of.

Why it is more times fire the bind for the messageProperty than the valueProperty? - it should be the same number of times.

As described above there simply is no guaranty about the number of updates.

Why I find that the code of the listener is fired more times when debug mode than normal execution?

In general debugging makes your program smaller. The smaller the update frequency from the thread of your Task, the smaller the number of updates between the times the Task class updates the properties and the smaller the number of skipped. (The updates are probably executed every frame or every few frames.) If you even use a break-point/stepper in the task, you probably make the Task extremely slow while the application thread runs at normal speed.


It should be easy enough to implement publish on your own by using a List to buffer the updates

public abstract class JavaFXWorker<S, T> extends Task<S> {

    private List<T> chunks = new ArrayList<>();
    private final Object lock = new Object();
    private boolean chunkUpdating = false;

    protected final void publish(T... results) {
        synchronized (lock) {
            chunks.addAll(Arrays.asList(results));
            if (!chunkUpdating) {
                chunkUpdating = true;
                Platform.runLater(() -> {
                    List<T> cs;
                    synchronized (lock) {
                        cs = chunks;
                        // create new list to not unnecessary lock worker thread
                        chunks = new ArrayList<>();
                        chunkUpdating = false;
                    }
                    try {
                        process(cs);
                    } catch (RuntimeException ex) {
                    }
                });
            }
        }
    }

    protected void process(List<T> chunks) {
    }

}

Sample use

@Override
public void start(Stage primaryStage) {

    ListView<Integer> lv = new ListView<>();

    Button btn = new Button("Run");
    btn.setOnAction((ActionEvent event) -> {
        JavaFXWorker<Void, Integer> worker = new JavaFXWorker<Void, Integer>() {

            @Override
            protected Void call() throws Exception {
                final int maxCount = 100;

                Random random = new Random();

                int breakIndex = random.nextInt(maxCount-1)+1;

                for (int i = 0; i < breakIndex; i++) {
                    publish(i);
                }
                // some break simulating a part long part of the task with no updates
                Thread.sleep(3000);
                for (int i = breakIndex; i <= maxCount; i++) {
                    publish(i);
                }

                return null;
            }

            @Override
            protected void process(List<Integer> chunks) {
                lv.getItems().addAll(chunks);
            }

        };
        new Thread(worker).start();
    });


    Scene scene = new Scene(new VBox(btn, lv));

    primaryStage.setScene(scene);
    primaryStage.show();
}

Upvotes: 2

Related Questions