6

我有这个数据流,大致:

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 格式化它的计算成本足够高,以至于给我的中央处理器; 实际上,我在视觉上关心的只是每秒最多 10-20 次变化。

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

4

5 回答 5

5

两个想法:

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

我倾向于第二种方法。

于 2012-02-08T22:17:59.663 回答
3

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

SwingWorkersprocess方法确实合并published块,因此它不会为每个发布的中间结果运行一次。

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

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

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

阅读更多关于SwingWorker:具有中期结果的任务

于 2012-02-08T22:42:53.990 回答
1

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

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

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

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

于 2012-02-08T22:25:24.663 回答
1

另一种可能性是将侦听器添加到您的生成器中,而不是直接对更改做出反应,您只需启动一个计时器。所以你的监听器看起来像(在某种伪代码中,因为我懒得启动我的 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

于 2012-02-08T23:57:33.067 回答
0

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

syncronized onChangeRequest() {
    if (flag) {
      flag = false;
      startTimer();
    }
}

timerEvent() {
    notify all your listeners;
}

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

于 2012-02-08T22:08:53.893 回答