Mise à jour de L'interface utilisateur à partir de différents threads de JavaFX

je suis en train de développer une application avec plusieurs TextField objets qui doivent être mis à jour pour refléter les changements dans les propriétés associées back-end. Les TextField s ne sont pas modifiables, seul le back-end peut changer leur contenu.

comme je le comprends, la bonne façon à ce sujet est d'exécuter le calcul lourd sur un thread séparé afin de ne pas bloquer L'UI. J'ai fait cela en utilisant javafx.concurrent.Task et j'ai communiqué une seule valeur au thread JavaFX en utilisant updateMessage() , qui a bien fonctionné. Cependant, j'ai besoin de plus d'une valeur à mettre à jour que la fin ne son croquant.

étant donné que les valeurs d'arrière-plan sont stockées sous la forme de propriétés JavaFX, j'ai simplement essayé de les lier au textProperty de chaque élément GUI et de laisser les fixations faire le travail. Cela ne fonctionne pas, cependant; après quelques instants de fonctionnement, le TextField s arrête la mise à jour, même si la tâche de fin de course est toujours en cours. Pas d'exceptions.

j'ai aussi essayé d'utiliser Platform.runLater() pour mettre à jour activement le TextField S plutôt que de lier. Le problème ici est que les tâches runLater() sont programmées plus rapidement que la plate-forme peut les exécuter, et donc L'interface graphique devient léthargique et a besoin de temps pour" rattraper " même après la tâche de fin de fin.

j'ai trouvé quelques questions ici:

Enregistreur entrées traduit de l'INTERFACE utilisateur cesse d'être mis à jour avec le temps

Multithreading dans JavaFX entraîne le blocage de l'INTERFACE utilisateur

mais mon problème persiste.

En résumé: j'ai un back-end à faire des changements de propriétés, et je veux ces modifications apparaissent sur l'interface graphique. La fin est un algorithme génétique, donc son fonctionnement est décomposé en générations discrètes. Ce que je voudrais, c'est que le TextField s se rafraîchisse au moins une fois entre les générations, même si cela retarde le la prochaine génération. Il est plus important que l'interface graphique réponde bien que l'AG fonctionne rapidement.

je peux poster quelques exemples de code si je n'ai pas été clair.

mise à JOUR

j'ai réussi à le faire en suivant la suggestion de James_D. Pour résoudre le problème du back-end à attendre l'impression de la console, j'ai mis en place une sorte de console tamponnée. Il stocke les ficelles à imprimer dans un StringBuffer et les ajoute effectivement à la TextArea quand une méthode flush() est appelée. J'ai utilisé un AtomicBoolean pour empêcher la prochaine génération de se produire jusqu'à ce que la chasse d'eau est complète, comme il est fait par un Platform.runLater() runnable. Notez également que cette solution est incroyablement lente.

28
demandé sur Community 2014-04-01 01:18:01

2 réponses

Je ne suis pas sûr de comprendre complètement, mais je pense que cela peut aider.

Utilisant La Plate-Forme.runLater(...) est une approche appropriée pour cela.

l'astuce pour éviter d'inonder le fil D'Application FX est d'utiliser une variable atomique pour stocker la valeur qui vous intéresse. Dans la Plate-forme.runLater(... méthode, le récupérer et de le régler à une valeur sentinelle. À partir de votre fil d'arrière-plan, mettez à jour la variable Atomic, mais ne produisez qu'un nouveau Plate.runLater(...) si elle a été ramenée à sa valeur sentinelle.

j'ai compris cela en regardant le code source pour la tâche . Regardez comment le message updateMessage(..) la méthode (ligne 1131 au moment de la rédaction) est mise en œuvre.

Voici un exemple qui utilise la même technique. Ceci a juste un thread de fond (occupé) qui compte aussi vite que possible, mettant à jour une Integrerproperty. Un observateur surveille cette propriété et met à jour un AtomicInteger avec la nouvelle valeur. Si la valeur actuelle de L'AtomicInteger est -1, Il programme une plate-forme.runLater ().

dans la plate-forme.runLater, je récupère la valeur de L'AtomicInteger et l'utilise pour mettre à jour un Label, en fixant la valeur à -1 dans le processus. Ceci indique que je suis prêt pour une autre mise à jour de L'interface utilisateur.

import java.text.NumberFormat;
import java.util.concurrent.atomic.AtomicInteger;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.property.IntegerProperty;
import javafx.beans.property.SimpleIntegerProperty;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.event.ActionEvent;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.layout.AnchorPane;
import javafx.stage.Stage;

public class ConcurrentModel extends Application {

  @Override
  public void start(Stage primaryStage) {

    final AtomicInteger count = new AtomicInteger(-1);

    final AnchorPane root = new AnchorPane();
    final Label label = new Label();
    final Model model = new Model();
    final NumberFormat formatter = NumberFormat.getIntegerInstance();
    formatter.setGroupingUsed(true);
    model.intProperty().addListener(new ChangeListener<Number>() {
      @Override
      public void changed(final ObservableValue<? extends Number> observable,
          final Number oldValue, final Number newValue) {
        if (count.getAndSet(newValue.intValue()) == -1) {
          Platform.runLater(new Runnable() {
            @Override
            public void run() {
              long value = count.getAndSet(-1);
              label.setText(formatter.format(value));
            }
          });          
        }

      }
    });
    final Button startButton = new Button("Start");
    startButton.setOnAction(new EventHandler<ActionEvent>() {
      @Override
      public void handle(ActionEvent event) {
        model.start();
      }
    });

    AnchorPane.setTopAnchor(label, 10.0);
    AnchorPane.setLeftAnchor(label, 10.0);
    AnchorPane.setBottomAnchor(startButton, 10.0);
    AnchorPane.setLeftAnchor(startButton, 10.0);
    root.getChildren().addAll(label, startButton);

    Scene scene = new Scene(root, 100, 100);
    primaryStage.setScene(scene);
    primaryStage.show();
  }

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

  public class Model extends Thread {
    private IntegerProperty intProperty;

    public Model() {
      intProperty = new SimpleIntegerProperty(this, "int", 0);
      setDaemon(true);
    }

    public int getInt() {
      return intProperty.get();
    }

    public IntegerProperty intProperty() {
      return intProperty;
    }

    @Override
    public void run() {
      while (true) {
        intProperty.set(intProperty.get() + 1);
      }
    }
  }
}

Si vous voulez vraiment de "conduire" l'extrémité arrière de l'INTERFACE: c'est de l'accélérateur, la vitesse du backend implémentation pour que vous voyez toutes les mises à jour, envisagez d'utiliser un AnimationTimer . Un AnimationTimer a un handle(...) qui est appelé une fois par rendu de cadre. Ainsi, vous pouvez bloquer l'implémentation back-end (par exemple en utilisant une file d'attente de blocage) et la libérer une fois par invocation de la méthode handle. La méthode handle(...) est invoquée sur le Thread D'Application FX.

la méthode handle(...) prend un paramètre qui est une estampille temporelle( en nanosecondes), donc vous pouvez l'utiliser pour ralentir la mises à jour supplémentaires, si une fois par image est trop rapide.

par exemple:

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;

import javafx.animation.AnimationTimer;
import javafx.application.Application;
import javafx.beans.property.LongProperty;
import javafx.beans.property.SimpleLongProperty;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.stage.Stage;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TextArea;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;


public class Main extends Application {
    @Override
    public void start(Stage primaryStage) {

        final BlockingQueue<String> messageQueue = new ArrayBlockingQueue<>(1);

        TextArea console = new TextArea();

        Button startButton = new Button("Start");
        startButton.setOnAction(event -> {
            MessageProducer producer = new MessageProducer(messageQueue);
            Thread t = new Thread(producer);
            t.setDaemon(true);
            t.start();
        });

        final LongProperty lastUpdate = new SimpleLongProperty();

        final long minUpdateInterval = 0 ; // nanoseconds. Set to higher number to slow output.

        AnimationTimer timer = new AnimationTimer() {

            @Override
            public void handle(long now) {
                if (now - lastUpdate.get() > minUpdateInterval) {
                    final String message = messageQueue.poll();
                    if (message != null) {
                        console.appendText("\n" + message);
                    }
                    lastUpdate.set(now);
                }
            }

        };

        timer.start();

        HBox controls = new HBox(5, startButton);
        controls.setPadding(new Insets(10));
        controls.setAlignment(Pos.CENTER);

        BorderPane root = new BorderPane(console, null, null, controls, null);
        Scene scene = new Scene(root,600,400);
        primaryStage.setScene(scene);
        primaryStage.show();
    }

    private static class MessageProducer implements Runnable {
        private final BlockingQueue<String> messageQueue ;

        public MessageProducer(BlockingQueue<String> messageQueue) {
            this.messageQueue = messageQueue ;
        }

        @Override
        public void run() {
            long messageCount = 0 ;
            try {
                while (true) {
                    final String message = "Message " + (++messageCount);
                    messageQueue.put(message);
                }
            } catch (InterruptedException exc) {
                System.out.println("Message producer interrupted: exiting.");
            }
        }
    }

    public static void main(String[] args) {
        launch(args);
    }
}
29
répondu James_D 2014-04-01 10:25:04

la meilleure façon d'effectuer ceci est d'utiliser Task dans JavaFx. C'est de loin la meilleure technique que j'ai trouvée pour mettre à jour les contrôles D'interface dans JavaFx.

Task task = new Task<Void>() {
    @Override public Void run() {
        static final int max = 1000000;
        for (int i=1; i<=max; i++) {
            updateProgress(i, max);
        }
        return null;
    }
};
ProgressBar bar = new ProgressBar();
bar.progressProperty().bind(task.progressProperty());
new Thread(task).start();
5
répondu zIronManBox 2015-06-23 11:20:16