edd*_*ddy 31 java user-interface multithreading javafx-2
我正在开发一个具有多个TextField对象的应用程序,这些对象需要更新以反映相关后端属性的更改.该TextFields为不可编辑,只有后端可能会改变他们的内容.
据我所知,正确的方法是在单独的线程上运行繁重的计算,以免阻塞UI.我这样做是使用javafx.concurrent.Task并将单个值传递回JavaFX线程updateMessage(),这很好用.但是,我需要更新多个值,因为后端会进行其处理.
由于后端值存储为JavaFX属性,我尝试将它们绑定到textProperty每个GUI元素,并让绑定完成工作.但是,这不起作用; 运行一会儿后,TextField即使后端任务仍在运行,s也会停止更新.没有例外.
我也尝试过使用Platform.runLater()主动更新TextFields而不是绑定.这里的问题是runLater()任务的调度速度比平台可以运行的速度快,因此GUI变得迟缓,即使在后端任务完成后也需要时间"赶上".
我在这里找到了一些问题:
但我的问题仍然存在.
总结:我有一个后端对属性进行更改,我希望这些更改出现在GUI上.后端是遗传算法,因此其操作被分解为离散的世代.我希望TextFields能够在几代人之间至少刷新一次,即使这会延迟下一代.更重要的是,GUI响应良好,而GA运行速度快.
如果我没有明确问题,我可以发布一些代码示例.
UPDATE
我按照James_D的建议设法做到了.为了解决后端必须等待控制台打印的问题,我实现了一个缓冲的控制台.它存储要在a中打印的字符串,StringBuffer并实际将它们附加到调用方法TextArea时flush().我使用AtomicBoolean来防止下一代发生,直到刷新完成,因为它是由Platform.runLater()runnable 完成的.另请注意,此解决方案非常慢.
Jam*_*s_D 34
不确定我是否完全理解,但我认为这可能会有所帮助.
使用Platform.runLater(...)是一种合适的方法.
避免泛滥FX应用程序线程的技巧是使用原子变量来存储您感兴趣的值.在Platform.runLater(...)方法中,检索它并将其设置为sentinel值.在后台线程中,更新Atomic变量,但只有在它被设置回其sentinel值时才发出新的Platform.runLater(...).
我通过查看Task的源代码来解决这个问题.看看如何实现updateMessage(..)方法(编写本文时的第1131行).
这是一个使用相同技术的例子.这只是一个(繁忙的)后台线程,它尽可能快地计算,更新IntegerProperty.观察者监视该属性并使用新值更新AtomicInteger.如果AtomicInteger的当前值为-1,则会调度Platform.runLater().
在Platform.runLater中,我检索AtomicInteger的值并使用它来更新Label,在过程中将值设置回-1.这表示我已准备好进行另一次UI更新.
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);
}
}
}
}
Run Code Online (Sandbox Code Playgroud)
如果你真的想从UI"驱动"后端:这会限制后端实现的速度,所以你看到所有的更新,考虑使用AnimationTimer.一个AnimationTimer具有handle(...)每帧渲染一次的调用.因此,您可以阻止后端实现(例如,通过使用阻塞队列),并在每次调用handle方法时释放一次.handle(...)在FX Application Thread上调用该方法.
该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);
}
}
Run Code Online (Sandbox Code Playgroud)
执行此操作的最佳方法是Task在JavaFx中使用.到目前为止,这是我在JavaFx中更新UI控件时遇到的最好的技术.
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();
Run Code Online (Sandbox Code Playgroud)