Ответ 1
Не уверен, что я полностью понимаю, но я думаю, что это может помочь.
Использование Platform.runLater(...) является подходящим подходом для этого.
Трюк, чтобы избежать наводнения потока приложений FX, - это использовать переменную Atomic для хранения интересующего вас значения. В методе Platform.runLater(...) извлеките его и установите для него значение сторожевой. Из фонового потока обновите Atomic-переменную, но выпустите новую платформу Platform.runLater(...), если она вернулась к ее значению.
Я понял это, посмотрев исходный код для задачи. Посмотрите, как реализуется метод updateMessage (..) (строка 1131 на момент написания).
Вот пример, который использует ту же технику. У этого просто есть (занятый) поток фона, который считается настолько быстрым, насколько это возможно, обновляя IntegerProperty. Наблюдатель наблюдает за этим свойством и обновляет AtomicInteger с новым значением. Если текущее значение AtomicInteger равно -1, оно планирует Platform.runLater().
В Platform.runLater я извлекаю значение AtomicInteger и использую его для обновления Label, устанавливая значение в -1 в процессе. Это означает, что я готов к другому обновлению пользовательского интерфейса.
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);
}
}
}
}
Если вы действительно хотите "управлять" задним концом с пользовательского интерфейса: это уменьшает скорость реализации бэкэнд, поэтому вы видите все обновления, подумайте об использовании AnimationTimer
. AnimationTimer
имеет handle(...)
, который вызывается один раз для каждого кадра. Таким образом, вы можете заблокировать реализацию на заднем плане (например, используя блокирующую очередь) и освободить ее один раз за вызов метода дескриптора. Метод handle(...)
вызывается в потоке приложения FX.
Метод handle(...)
принимает параметр, который является меткой времени (в наносекундах), поэтому вы можете использовать это, чтобы замедлять обновления, если один раз для каждого кадра слишком быстро.
Например:
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);
}
}