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();
        }
    }
}
The content of this article comes from the network collection of netizens. It is used as a learning reference. The copyright belongs to the original author.
THE END
分享
二维码
< <上一篇
下一篇>>