【问题标题】:Is this a valid way to minimize binding invalidations?这是最小化绑定失效的有效方法吗?
【发布时间】:2017-08-26 02:33:28
【问题描述】:

我有一些复杂的 Observable 结构,它们可能是也可能不是坏主意,但这不是这个问题的重点。

这些结构的问题在于它们会导致 UI 显示的 Observable 对象的大量失效。据我所知,当 JavaFX UI 显示某些内容时,它会在其上注册一个 ChangeListener,因此任何使用惰性求值的尝试都会消失。也就是说,使 observable 无效似乎是在告诉 UI 它可能已更改,这会导致 UI 立即请求它的值,迫使它立即进行评估。

所以,我有了通过Platform.runLater() 推迟失效的想法。

我创建了一个名为DeferredBinding 的类,它将所有内容都委托给一个包装好的Binding,除了invalidate() 方法,它推迟到稍后处理的JavaFX UI 线程。它似乎工作......我可以使无效一百次,它似乎实际上只处理一次无效。

但是,我以前没有见过这种模式,我担心它可能属于“尝试不错但想法不好”的类别。

那么,问题:这是一个坏主意吗?我特别担心引入其他依赖于DeferredBindingObservable 对象的错误。一旦Platform.runLater() 发生,他们会好吗?

package com.myapp.SAM.model.datastructures;

import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Logger;

import javafx.application.Platform;
import javafx.beans.InvalidationListener;
import javafx.beans.binding.Binding;
import javafx.beans.value.ChangeListener;
import javafx.collections.ObservableList;

/**
 * Specialized binding that defers its invalidations to the JavaFX UI thread in a throttled manner. The idea being that, if invalidate() is called many times,
 * it only basically happens once (when the UI thread gets to it).
 */
public class DeferredBinding<T> implements Binding<T> {

    private static final Logger logger = Logger.getLogger(DeferredBinding.class.getName());

    private final Binding<T> binding;

    private final AtomicBoolean pendingInvalidation = new AtomicBoolean(false);

    public DeferredBinding(Binding<T> binding) {
        this.binding = binding;
    }

    @Override
    public void addListener(ChangeListener<? super T> listener) {
        binding.addListener(listener);
    }

    @Override
    public void removeListener(ChangeListener<? super T> listener) {
        binding.removeListener(listener);
    }

    @Override
    public T getValue() {
        return binding.getValue();
    }

    @Override
    public void addListener(InvalidationListener listener) {
        binding.addListener(listener);
    }

    @Override
    public void removeListener(InvalidationListener listener) {
        binding.removeListener(listener);
    }

    @Override
    public boolean isValid() {
        return binding.isValid();
    }

    /**
     * Override logic for invalidate() method to defer invalidation to runLater. Throttle the invalidations so as not to floor the JavaFX UI thread with
     * multiple calls
     */
    @Override
    public void invalidate() {
        if (pendingInvalidation.getAndSet(true) == false) {
            Platform.runLater(() -> {
                // Signal that the UI is processing the pending invalidation, so any additional invalidations must schedule another update.
                pendingInvalidation.set(false);
                binding.invalidate();
            });
        }
    }

    @Override
    public ObservableList<?> getDependencies() {
        return binding.getDependencies();
    }

    @Override
    public void dispose() {
        binding.dispose();
    }

}

【问题讨论】:

  • 它类似于 Service 实现中使用的机制来避免更新淹没 UI,但是您有一个单独的线程,其中可能包含大量更新。我担心的是为什么你有一个结构,你需要在一个动作中一遍又一遍地更新相同的属性。
  • 这是一个涉及聚合的复杂结构。我有一个List,UI 需要不断地实时显示聚合和基于这些聚合的计算。问题是如果我需要更新 List 中的 60,000 个元素,聚合将重新计算 60,000 次(只有最后一个是真正相关的)。

标签: java javafx concurrency javafx-8


【解决方案1】:

我不会尝试提前解决性能问题。衡量您的应用程序以确定您是否有问题,然后继续。

假设您有一个问题,有很多方法可以解决您的抽象问题。我想到了三个解决方案:

1

Coalesce 更新 (Platform.runLater()) 以防止 FX 事件队列饱和,就像您在示例中所做的那样。

而且由于您只是使绑定无效,因此您不必担心在途中丢失值。所以看起来(不知道完整的应用程序)这种方式应该可行。


2

使用Nodes 的内置行为将区域标记为脏以进行(重新)布局。在某些时候,您会调用 javafx.scene.Parent.requestLayout()(这是“无效”),它会在将来的某个时候调用 javafx.scene.Parent.layoutChildren() 以将脏属性应用到该区域。


3

以不同的方式使用解决方案 2:使用虚拟化布局容器。 TableViewTreeTableViewListView 都使用虚拟化方法仅更新可见单元格。 有关 JDK 示例,请参阅 com.sun.javafx.scene.control.skin.VirtualFlowcom.sun.javafx.scene.control.skin.VirtualContainerBase,另一个示例是 Flowless API

由于您没有告诉任何细节,我无法进一步指导您。但应该清楚的是,您的方法可能非常有效,还有其他方法。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-11-02
    • 2013-05-02
    • 2012-01-06
    • 2020-10-02
    • 1970-01-01
    • 2012-12-21
    相关资源
    最近更新 更多