Java异步数据加载进度和线程噩梦

时间:2012-01-06 13:03:22

标签: java swing user-interface asynchronous load

所有

我们的应用程序中存在数据加载问题。当前实现的目的是为数据加载提供异步机制。

我基本上想知道是否有任何良好的模式或做法。

该应用程序具有许多不以标准方式编码的不同UI组件。它全部由配置(即xml)生成。通过单击日历来触发数据加载,日历将向数据加载器发送异步消息,其中大约有6个实例(同一类)将从数据库加载数据。一旦加载并处理了数据,数据加载器类就会将表数据异步发送到要显示的JTable。

由于数据加载可能需要大约一秒钟到1或2分钟,因此需要一个进度对话框。编写了一个附加类,用于在启动/完成数据加载时侦听来自数据加载器的事件。当任何数据加载器开始加载时,将显示一个对话框,指示数据正在加载。当最后一个数据加载器完成时,应隐藏该对话框。

当前问题/问题

发生了两个问题:

  • 有时,即使所有数据加载器都已完成,对话框仍然可见。
  • Othertimes,数据可能不会显示在表格中。

我们试图在显示和隐藏进度的方法周围添加synchronized关键字,但仍然得到类似的发布。我认为某些重写可能是有序的,但我想了解更多关于异步java模式的内容。该机制需要更加强大和可靠。

当前流程

如果您对我们当前的流程感兴趣,我已尝试总结以下事件。这可能会对设计/实施中的问题有所了解。

  1. 用户点击日历中的日期。

    日历组件向每个数据加载器实例发送一条消息,告诉他们开始加载。此消息在日历上以异步方式(2)发送。

  2. 数据加载器接收加载消息

    每个DataLoader负责加载相关数据并将其传递给要显示的表。

    每个数据加载器都有一个加载数据的线程实例。如果设置了当前线程加载实例并且正在加载数据,则中止加载 - 这可能是通过快速单击日历中的天数来实现的。一旦数据加载线程空闲或为空,就会创建并启动一个新的线程实例。

    然后,数据加载线程执行数据访问和处理。启动时,会通知DataLoadDialog类(3)。此DataLoadDialog基本上将一个侦听器(DataLoaderListener扩展EventListener)添加到EventListenerList。加载数据线程时,将调用DataLoaderListener.loadStart,由DataLoadDialog拾取。当线程加载并处理数据时,将调用DataLoaderListener.loadComplete。这再次在DataLoadDialog中被选中。

    现在数据已加载,DataLoader将数据发送到要显示的表。

  3. 通知负载状态的DataLoadDialog

    DataLoadDialog具有对每个数据加载器的引用。每个数据加载器都有一个状态,可以通过getter访问,该getter返回一个空闲或加载的枚举。每当DataLoadDialog收到loadStart或loadComplete的调用时,它都会根据数据加载线程的状态来确定是否显示/隐藏进度对话框。

    进程对话框是一个单例静态实例,它是在创建类时创建的(来自配置xml)。

  4. 任何帮助将不胜感激。如果我遗漏了任何内容,请添加评论,我会更新问题。

    谢谢,

    Andez

3 个答案:

答案 0 :(得分:3)

这些问题听起来像违反基本Swing规则:所有组件访问必须在EDT上发生。确保在EDT上触发所有用户反馈(显示/隐藏对话框,更新过程状态,更新表数据)。

有助于这样做的有用的类是SwingWorker,它的api doc非常广泛,并且有关于如何直接访问模型/组件的示例。

一些伪代码片段,显示如何通过propertyChangeEvent传递中间数据,使工作者与实际的ui分离:

// on user click (here we are on EDT), 
// open the dialog, create the loader, add a PropertyChangeListener and start 
showProcessDialog();
MySwingWorker worker = new MySwingWorker();
PropertyChangeListener l = new PropertyChangeListener() {
     public void propertyChanged(....) {
         if (StateValue.DONE == evt.getNewValue) {
             closeProcessDialog();
         }
         if ("chunkAvailable".equals(evt.getPropertyName()) {
             addChunkToTable((ChunkType) evt.getNewValue());
         }
     }    
}
worker.addPropertyChangeListener(l);
worker.execute();

// custom implementation of SwingWorker
public MySwingWorker extends SwingWorker<ResultType, ChunkType> {

    @Override
    protected  <ResultType> doInBackground() {
         // here we are in the worker thread 
         // start the actual loaders 
         Dataloaders loaders = ....
         // process their result/s 
         ResultType endResult = ...;
         while (...) {
             ChunkType intermediateResult = ....
             // possibly produce some end result
             endResult = ... 
             // publish the intermediate chunk 
             publish(intermediateResult); 
         }
         return endResult; 
    }

    @Override
    protected void process(List<ChunkType> chunks) {
         // here we are on the EDT, so it's safe to either notify swing listener
         // or access swing components/models directly (as shown in the api/tutorial example) 
         for(ChunkType chunk: chunks) {
             PropertyChangeEvent e = new PropertyChangeEvent(this, "chunkAvailable", null, chunk);
             getPropertyChangeSupport().firePropertyChange(e);
         } 
    }

}

答案 1 :(得分:2)

除了@ kleopatra的有说服力的建议外,引用的article here还讨论了在现有代码中查找EDT违规行为的方法。更多示例herehere

答案 2 :(得分:0)

我查看了SwingWorker。我完成了这些示例,但我的想法是从SwingWorker中生成每个DataLoader - 我认为。

我决定不这样做,并发现DataLoadDialog类和Swing JDialog之间的交互进展方式存在问题。我太简化了我的例子。还有2个额外的组合也触发了数据加载。一个组合将强制3个数据加载器开始加载,另一个组合用于另外3个数据加载器。这将迫使我添加额外的配置,由于系统的编写方式,这些配置会很尴尬。

我最初尝试将synchronized关键字添加到loadStart和loadComplete,但仍未解决问题。我还在invokeAndWait中显示/隐藏了对话框。我希望这会阻止多个线程尝试显示/隐藏进度 - 但事实并非如此。我用invokeLater替换了invokeAndWait,这似乎可以解决问题。

我完全同意SwingWorker,这样的场景应该在设计中更多地布局 - 我们在这里不好 - 我讨厌。整个并发解决方案在花了大约20分钟同步加载初始数据后放在一起--1个加载器依次从数据库中检索数据。加载的数据量非常可怕 - 而且还必须经过某种调节。

Andez