JavaFX ChangeListener并不总是有效

我有一个JavaFX应用程序,并在那里有一个并发任务。 当Task正在运行时,我想将updateMessage()中的消息附加到TextArea

因为绑定不会将新文本追加到TextArea,所以我使用了ChangeListener

worker.messageProperty().addListener((observable, oldValue, newValue) -> { ta_Statusbereich.appendText("\n" + newValue); }); 

这是有效的,但不是每次改变。 我用System.out.println()检查了它,并在1到300的任务中计算

 for (Integer i = 1; i <= 300; i++) { updateMessage(i.toString()); System.out.println(i.toString()); } 

任务中的println()给了我想要的1,2,3,4,5,6,7,8等等,但我的TextArea显示1,4,5,8,9我然后添加了一个println in ChangeListener得到相同的结果,1,4,5,8,9(结果是随机的,不总是1,4,5 ……)

为什么? 有没有其他方法将消息文本附加到TextAres,可能与绑定?

message属性被设计为一个属性,它保存task的“当前消息”:即目标用例类似于状态消息。 在这个用例中,从不拦截非常短暂时间内存储在属性中的消息并不重要。 实际上, updateMessage()的文档指出:

对updateMessage的调用将在FX应用程序线程上合并并稍后运行,因此即使从FX Application线程调用updateMessage也不一定会立即更新此属性,并且可以合并中间消息值以节省事件通知

(我的重点)。 因此,简而言之,传递给updateMessage(...)某些值可能永远不会被设置为messageProperty的值,如果它们被另一个值快速取代。 通常,每次将帧渲染到屏幕时(每秒60次或更少),只能观察到一个值。 如果您有一个重要的用例,您希望观察每个值,那么您需要使用其他机制。

一个非常天真的实现只会使用Platform.runLater(...)并直接更新文本区域。 我不推荐这种实现,因为你冒着使用过多调用(这是updateMessage(...)合并调用的确切原因updateMessage(...)充斥FX应用程序线程的风险,使得UI无响应。 但是,此实现看起来像:

 for (int i = 1 ; i <= 300; i++) { String value = "\n" + i ; Platform.runLater(() -> ta_Statusbereich.appendText(value)); } 

另一种选择是使每个操作成为一个单独的任务,并在一些执行程序中并行执行它们。 附加到每个任务的onSucceeded处理程序的文本区域。 在此实现中,结果的顺序不是预先确定的,因此如果顺序很重要,则这不是一个合适的机制:

 final int numThreads = 8 ; Executor exec = Executors.newFixedThreadPool(numThreads, runnable -> { Thread t = Executors.defaultThreadFactory().newThread(runnable); t.setDaemon(true); return t ; }); // ... for (int i = 1; i <= 300; i++) { int value = i ; Task task = new Task() { @Override public String call() { // in real life, do real work here... return "\n" + value ; // value to be processed in onSucceeded } }; task.setOnSucceeded(e -> ta_Statusbereich.appendText(task.getValue())); exec.execute(task); } 

如果您想从单个任务完成所有这些操作并控制顺序,那么您可以将所有消息放入BlockingQueue ,从阻塞队列中获取消息并将它们放在FX Application线程的文本区域中。 为了确保您不会使用过多的调用来泛滥FX应用程序线程,您应该每帧渲染一次消息来自队列的消息不超过一次呈现给屏幕。 您可以为此目的使用AnimationTimer :保证每帧渲染一次调用handle方法。 这看起来像:

 BlockingQueue messageQueue = new LinkedBlockingQueue<>(); Task task = new Task() { @Override public Void call() throws Exception { final int numMessages = 300 ; Platform.runLater(() -> new MessageConsumer(messageQueue, ta_Statusbereich, numMessages).start()); for (int i = 1; i <= numMessages; i++) { // do real work... messageQueue.put(Integer.toString(i)); } return null ; } }; new Thread(task).start(); // or submit to an executor... // ... public class MessageConsumer extends AnimationTimer { private final BlockingQueue messageQueue ; private final TextArea textArea ; private final numMessages ; private int messagesReceived = 0 ; public MessageConsumer(BlockingQueue messageQueue, TextArea textArea, int numMessages) { this.messageQueue = messageQueue ; this.textArea = textArea ; this.numMessages = numMessages ; } @Override public void handle(long now) { List messages = new ArrayList<>(); messagesReceived += messageQueue.drainTo(messages); messages.forEach(msg -> textArea.appendText("\n"+msg)); if (messagesReceived >= numMessages) { stop(); } } }