JavaFX ChangeListener并不总是有效

Gar*_*rog 6 java concurrency javafx changelistener

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

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

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

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

for (Integer i = 1; i <= 300; i++) {
    updateMessage(i.toString());
    System.out.println(i.toString());
}
Run Code Online (Sandbox Code Playgroud)

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

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

Jam*_*s_D 15

message属性被设计为一个属性,其中包含以下内容的"当前消息" task:即目标用例类似于状态消息.在这个用例中,是否永远不会截获存储在属性中的消息只是非常短暂的时间并不重要.的确,国家的文件updateMessage():

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

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

一个非常天真的实现只会使用Platform.runLater(...)并直接更新文本区域.我不推荐这种实现方式,因为您可能会因为太多调用而导致FX应用程序线程泛滥(这是updateMessage(...)合并调用的确切原因),从而使UI无响应.但是,此实现看起来像:

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

另一种选择是使每个操作成为一个单独的任务,并在一些执行器中并行执行它们.附加到每个任务的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);
}
Run Code Online (Sandbox Code Playgroud)

如果您想从单个任务完成所有这些操作并控制顺序,那么您可以将所有消息放入a中BlockingQueue,从阻塞队列中获取消息并将它们放在FX Application线程的文本区域中.为了确保不会使用过多的调用来泛洪FX应用程序线程,您应该每帧渲染一次消息来自队列的消息不超过一次呈现给屏幕.您可以使用a 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();
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 像这样的答案是我在这个平台上提出要求的原因!哇,太棒了,非常感谢你.像我这样只有一些sciolism的人.拥有如此多的信息和知识......当你不得不从api描述中挖掘出那些东西时...地狱男人......我试过了runlater但是在你在本地机器上重命名文件而不是在网络.顺序也很重要,因为我计算文件并将数字打印到屏幕上.所以最后我必须选择3号.:) (2认同)
  • @Thylossus对于某些UI任务,ListView可能比TextArea更合适,因为ListView是一个虚拟化控件.请参阅[通过线程将消息记录到JavaFX的最有效方式](http://stackoverflow.com/questions/24116858/most-efficient-way-to-log-messages-to-javafx-textarea-via-threads -with-simple-cu)了解基于ListView的方法的更多信息.ListView可以在这种情况下提供帮助的原因是它只需要渲染可见线,而不是文本区域中的所有文本. (2认同)