JavaFX changelistener is not always valid
I have a JavaFX application and have a concurrent task there
Because the binding does not append new text to the textarea, I use changelistener
worker.messageproperty().addListener((observable,oldValue,newValue) -> { ta_Statusbereich.appendText("\n" + newValue); });
This is effective, but not every change I use system out. Println () checks it and calculates it in tasks from 1 to 300
for (Integer i = 1; i <= 300; i++) { updateMessage(i.toString()); System.out.println(i.toString()); }
Println () in the task gives me 1, 2, 3, 4, 5, 6, 7, 8 and so on, but my textarea shows 1, 8, 9. Then I add a println in changelistener and get the same result, 1, 9 (the result is not always 1, 5...)
Why? Is there any other way to attach message text to textares, perhaps bound to?
Solution
The message attribute is designed to hold the "current message" attribute of the task: that is, the target use case is similar to the status message In this use case, it doesn't matter whether the message stored in the attribute will never be intercepted for a very short time Indeed, documentation for updatemessage() means:
(my point) Therefore, in short, some values passed to updatemessage (...) may never be set to the value of messageproperty if they are quickly replaced by another value Typically, you can only observe one value each time you render a frame to the screen (60 times per second or less) If you have an important use case and you want to observe each value, you need to use another mechanism
A very naive implementation will only use platform Runlater (...) and update the text area directly I don't recommend this implementation, because you may cause excessive calls to overflow FX application threads (the exact reason for updatemessage (...) merge calls), resulting in no response from the UI However, this implementation looks like:
for (int i = 1 ; i <= 300; i++) { String value = "\n" + i ; Platform.runLater(() -> ta_Statusbereich.appendText(value)); }
Another option is to make each operation a separate task and execute them in parallel in some actuators The text area of the onsucceeded handler attached to each task In this implementation, the order of results is not predetermined, so if the order is important, this is not an appropriate mechanism:
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<String> task = new Task<String>() { @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); }
If you want to complete all these operations from a single task and control the order, you can put all messages into the BlockingQueue, get messages from the blocking queue and put them in the text area of the FX application thread To ensure that FX application threads are not flooded with too many calls, you should render messages once per frame, and messages from the queue should not be rendered to the screen more than once You can use animationtimer to do this: ensure that the handle method is called once per frame rendering This looks like:
BlockingQueue<String> messageQueue = new LinkedBlockingQueue<>(); Task<Void> task = new Task<Void>() { @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<String> messageQueue ; private final TextArea textArea ; private final numMessages ; private int messagesReceived = 0 ; public MessageConsumer(BlockingQueue<String> messageQueue,TextArea textArea,int numMessages) { this.messageQueue = messageQueue ; this.textArea = textArea ; this.numMessages = numMessages ; } @Override public void handle(long Now) { List<String> messages = new ArrayList<>(); messagesReceived += messageQueue.drainTo(messages); messages.forEach(msg -> textArea.appendText("\n"+msg)); if (messagesReceived >= numMessages) { stop(); } } }