The message
property is designed as a property which holds a "current message" for the task
: i.e. the target use case is something akin to a status message. In this use case, it doesn't matter if a message that is stored in the property for only a very brief time is never intercepted. Indeed, the documentation for updateMessage()
states:
Calls to updateMessage are coalesced and run later on the FX
application thread, so calls to updateMessage, even from the FX
Application thread, may not necessarily result in immediate updates to
this property, and intermediate message values may be coalesced to
save on event notifications.
(my emphasis). So, in short, some values passed to updateMessage(...)
may never actually be set as the value of messageProperty
if they are superceded quickly by another value. In general, you can expect only one value to be observed every time a frame is rendered to the screen (60 times per second, or fewer). If you have a use case where it is important you want to observe every value, then you need to use another mechanism.
A very naïve implementation would just use Platform.runLater(...)
and directly update the text area. I do not recommend this implementation, as you risk flooding the FX Application Thread with too many calls (the exact reason why updateMessage(...)
coalesces calls), making the UI unresponsive. However, this implementation would look 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 all in parallel in some executor. Append to the text area in each task's onSucceeded
handler. In this implementation, the order of the results is not predetermined, so if 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 do all this from a single task, and control the order, then you can put all the messages into a BlockingQueue
, taking messages from the blocking queue and placing them in the text area on the FX Application thread. To ensure you don't flood the FX Application thread with too many calls, you should consume the messages from the queue no more than once per frame rendering to the screen. You can use an AnimationTimer
for this purpose: it's handle
method is guaranteed to be invoked 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();
}
}
}