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

因为绑定(bind)不会将新文本追加到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
然后我在ChangeListener中添加了一个println,得到相同的结果1,4,5,8,9
(结果是随机的,并不总是1,4,5 ...)

为什么 ?还有其他方法可以将消息文本附加到TextAres,也许可以使用bind?

最佳答案

message属性被设计为保存task的“当前消息”的属性:即目标用例类似于状态消息。在此用例中,仅在非常短的时间内存储在属性中的消息就不会被截取并不重要。实际上,documentation for updateMessage() 指出:



(我的重点)。因此,简而言之,如果传递给updateMessage(...)的某些值迅速被另一个值所取代,则可能实际上不会设置为messageProperty的值。通常,每次将一帧渲染到屏幕上时,您都只能观察到一个值(每秒60次或更少)。如果您有一个重要的用例,那就要观察每个值,那么您就需要使用另一种机制。

一个非常幼稚的实现将只使用Platform.runLater(...)并直接更新文本区域。我不推荐这种实现方式,因为您冒着太多调用淹没FX Application Thread的风险(updateMessage(...)合并调用的确切原因),使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<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);
}

如果要通过单个任务完成所有操作并控制顺序,则可以将所有消息放入BlockingQueue中,从阻止队列中获取消息,然后将其放置在FX Application线程的文本区域中。为了确保您不会在FX Application线程中进行过多的调用,每帧渲染到屏幕上,从队列中消耗的消息应不超过一次。您可以为此使用 AnimationTimer :它的handle方法保证每个帧渲染都被调用一次。看起来像:
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();
        }
    }
}

关于java - JavaFX ChangeListener并非始终有效,我们在Stack Overflow上找到一个类似的问题:https://stackoverflow.com/questions/31408363/

10-10 20:16