【问题标题】:Java + Swing: writing code to coalesce change eventsJava + Swing:编写代码来合并更改事件
【发布时间】:2012-03-01 09:42:30
【问题描述】:

我有这个数据流,大致如下:

DataGenerator -> DataFormatter -> UI

DataGenerator 是快速生成数据的东西; DataFormatter 是为了显示目的而对其进行格式化的东西;而 UI 只是一堆 Swing 元素。

我想让我的 DataGenerator 变成这样:

class DataGenerator
{
   final private PropertyChangeSupport pcs;
   ...
   public void addPropertyChangeListener(PropertyChangeListener pcl) {
     this.pcs.addPropertyChangeListener(pcl); 
   }
   public void removePropertyChangeListener(PropertyChangeListener pcl) {
     this.pcs.removePropertyChangeListener(pcl);
   }
}

只要我的数据生成器有新数据,只需致电this.pcs.firePropertyChange(...);然后我可以只做dataGenerator.addPropertyListener(listener),其中listener 负责将更改推送到DataFormatter,然后再推送到UI。

这种方法的问题在于,每秒有数千个 dataGenerator 更改(每秒 10,000 到 60,000 次,具体取决于我的情况),并且为 UI 格式化它的计算成本足够高,以至于它不需要在我的 CPU 上加载;实际上,我在视觉上关心的只是每秒最多 10-20 次变化。

有没有办法使用类似的方法,但在更改事件到达 DataFormatter 之前合并它们?如果我收到关于单个主题的多个更新事件,我只关心显示最新的,可以跳过所有以前的。

【问题讨论】:

  • 如何使用最后一次 UI 更新的 System.nanoTime 保持一个长值,如果更新后 N ns 发生属性更改事件,则忽略它们?
  • 我想到了这一点(见鬼,System.currentTimeMillis() 会起作用——我只关心相对于人类感知而言事情是否很快),但是有一个问题。假设您在视觉更新后 100 微秒有一个更改事件,因此 DataFormatter / UI 会忽略该更改。现在由于某种原因,DataGenerator 停止产生更新(它不一定是每秒连续 10-60K 事件)。糟糕,您错过了最近的更改。这很糟糕。

标签: java swing event-queue


【解决方案1】:

听起来您的DataGenerator 在 EDT 线程上做了很多非 GUI 工作。我建议您的 DataGenerator 扩展 SwingWorker 并在后台线程中完成工作,在 doInBackground 中实现。然后 SwingWorker 可以 publish 将中间结果发送到 GUI,而您有一个 process 方法接收 EDT 上最近发布的几个块并更新您的 GUI。

SwingWorkers process 方法执行 coalesce published 块,因此它不会为每个发布的中间结果运行一次。

如果您只关心 EDT 上的最后一个结果,您可以使用此代码,它只关心列表中的最后一个块:

 @Override
 protected void process(List<Integer> chunks) {

     // get the *last* chunk, skip the others
     doSomethingWith( chunks.get(chunks.size() - 1) );
 }

阅读更多SwingWorker: Tasks that Have Interim Results

【讨论】:

  • DataGenerator 不在 EDT 线程中,并且由于各种原因发生在由 ExecutorService 管理的另一个线程中。
  • @JasonS:我明白了。但是SwingWorker 也可以在ExecutorService 上运行,请参阅SwingWorker-文档:Because SwingWorker implements Runnable, a SwingWorker can be submitted to an Executor for execution.
  • @JasonS:在我对Stop/cancel SwingWorker thread?的回答中,我有一个关于如何使用来自 SwingWorker 的最新中间结果的完整示例
【解决方案2】:

另一种可能性是将侦听器添加到您的生成器中,而不是直接对更改做出反应,您只需启动一个计时器。所以你的监听器看起来像(在某种伪代码中,因为我懒得启动我的 IDE 或查找确切的方法签名)

Timer timer = new Timer( 100, new ActionListener(){//update the UI in this listener};

public void propertyChange( PropertyChangeEvent event ){
 if ( timer.isRunning() ){
   timer.restart();
 } else {
   timer.start();
 }
}

除非您的数据生成器一直在生成数据,或者您还需要中间更新,否则这将起作用。在这种情况下,您可以删除timer.restart() 调用,或选择此线程中的任何其他建议(轮询机制或SwingWorker

【讨论】:

    【解决方案3】:

    两个想法:

    • 聚合PropertyChangeEvents。扩展 PropertyChangeSupport,覆盖 public void firePropertyChange(PropertyChangeEvent evt),仅当最后一个事件在 50 毫秒(或任何看起来合适的时间)之前触发时触发。 (实际上,您应该覆盖每个 fire* 方法,或者至少覆盖您在场景中使用的方法,以防止创建 PropertyChangeEvent。)
    • 放弃整个事件的接近。每秒 60.000 个事件是一个相当高的数字。在这种情况下,我会投票。这是对 MVP 的概念更改,演示者知道它是否处于活动状态并应该轮询。使用这种方法,您不会产生数千个无用的事件;无论有多少数据,您甚至可以呈现每秒可能的最高帧数。或者,您可以将 Presenter 设置为固定速率,让它在两次刷新之间休眠一段时间,或者让它适应其他情况(如 CPU 负载)。

    我倾向于第二种方法。

    【讨论】:

    • 好的。对于您的第二种方法,有没有办法使用轮询(我可以使用 AtomicBoolean 轻松保留某种“已更改”标志)但仍然保持双方之间的脱钩?我不想让我的 DataGenerator 知道它的下游客户端;此外,我的问题陈述有点简化:实际上有 256 个 DataGenerators,每秒总共有 10-60K 的变化。 (大多数时候只有少数是活跃的,但我无法预测在任何给定的瞬间哪些是活跃的。这是一个很长的故事。)属性更改方法很好,因为我只是在必要时收到通知。
    • 但是您仍然必须在开始时在某些情况下进行注册。如果您存储用于轮询或调用“reference.addPropertyChangeListener(this)”的引用,这有关系吗?我同意拥有一个活动的、解耦的模型可能是一个更清晰的概念——但不是这个数据量(并且 MVP 模式出于这些原因改变了职责)。我会写一个Aggregator,它知道你的DataGenerators,收集数据并且可以由演示者轮询。事实上,这个Aggregator 甚至可以每秒发送 20 个事件。我只是不认为 60.000 个事件处理有效或有用。
    • 刚刚发布了关于此主题的interesting blog entry
    【解决方案4】:

    您可以使用大小为 1 的 ArrayBlockingQueue,在其中您可以使用 offer() 函数推送您的数据(意味着如果队列已满,它什么也不做)

    然后创建一个定期轮询队列的ScheduledThreadPoolExecutor

    这样你就可以放松生成和显示之间的耦合。

    生成器 -> 队列 -> 格式化/显示

    【讨论】:

    • 但我不想排队;我想合并事件。如果 DataGenerator 说“我的价值是蓝色的!”然后是“我的价值是红色的!”然后是“我的价值是紫色的!”我不关心前两个,我只关心最新的。
    【解决方案5】:

    如果您编写自己的带有阻塞标志和计时器的小更改侦听器,您可以:

    syncronized onChangeRequest() {
        if (flag) {
          flag = false;
          startTimer();
        }
    }
    
    timerEvent() {
        notify all your listeners;
    }
    

    我相信实际上有一个很好的阻塞并发标志可以使用,但我一生都记不住它叫什么!

    【讨论】:

      猜你喜欢
      • 2012-09-20
      • 2011-08-28
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-06-04
      • 1970-01-01
      • 2018-10-30
      • 1970-01-01
      相关资源
      最近更新 更多