Java асинхронная загрузка данных с прогрессом и ночные кошмары - PullRequest
3 голосов
/ 06 января 2012

All

У нас проблемы с загрузкой данных в нашем приложении. Целью текущей реализации было создание асинхронного механизма загрузки данных.

Я в основном хочу знать, есть ли какие-либо хорошие модели или практики для этого.

Приложение имеет множество различных компонентов пользовательского интерфейса, которые не кодируются стандартным способом. Все это генерируется из конфигурации (то есть xml). Загрузка данных запускается нажатием на календарь, который отправляет асинхронное сообщение загрузчикам данных, из которых существует около 6 экземпляров (того же класса), которые загружают данные из базы данных. После того как данные загружены и обработаны, класс загрузчика данных затем асинхронно отправит данные таблицы в таблицу JTable, в которой они будут отображаться.

Поскольку загрузка данных может занять от секунды или около того до 1 или 2 минут, требовался диалог прогресса. Был написан дополнительный класс для прослушивания событий от загрузчиков данных при запуске / завершении загрузки данных. Когда любой загрузчик данных начинает загрузку, отображается диалоговое окно, указывающее, что данные загружались. Когда последний загрузчик данных завершил, диалог должен быть скрыт.

Текущие проблемы / проблемы

Возникают 2 проблемы:

  • Иногда диалоговое окно остается видимым, даже если все загрузчики данных завершили.
  • В других случаях данные могут не отображаться в таблице.

Мы тщетно пытались добавить синхронизируемое ключевое слово вокруг методов отображения и скрытия прогресса, но все равно получаем аналогичные. Я думаю, что какое-то переписывание может быть в порядке, но я хочу понять немного больше об асинхронных шаблонах Java. Механизм должен быть более надежным и надежным.

Текущий процесс

Если вы заинтересованы в нашем текущем процессе, я попытался обобщить события ниже. Это может пролить свет на проблемы в разработке / реализации.

  1. Пользователь нажимает на день в календаре.

    Компонент календаря отправляет сообщение каждому экземпляру загрузчика данных с указанием начать загрузку. Это сообщение отправляется из календаря асинхронно в отдельном потоке (2).

  2. Загрузчик данных получает сообщение о загрузке

    Каждый DataLoader отвечает за загрузку соответствующих данных и передачу их в таблицу для отображения.

    Каждый загрузчик данных имеет отдельный экземпляр потока, в который загружаются данные. Если текущий экземпляр загрузки потока установлен и загружает данные, тогда загрузка прерывается - что может произойти из-за быстрого щелчка по дням в календаре. Новый экземпляр потока создается и запускается, когда поток загрузки данных простаивает или обнуляется.

    Поток загрузки данных затем выполняет доступ к данным и их обработку. При запуске класс DataLoadDialog получает уведомление (3). Этот DataLoadDialog в основном добавляет прослушиватель (DataLoaderListener расширяет EventListener) к EventListenerList. Когда поток данных загружается, вызывается DataLoaderListener.loadStart, который захватывается DataLoadDialog. Когда поток загружает и обрабатывает данные, вызывается DataLoaderListener.loadComplete. Это снова фиксируется в DataLoadDialog.

    Теперь, когда данные загружены, DataLoader отправляет данные в таблицу для отображения.

  3. Inform DataLoadDialog статуса загрузки

    DataLoadDialog имеет ссылку на каждый загрузчик данных. Каждый загрузчик данных имеет статус, доступ к которому осуществляется через геттер, который возвращает перечисление Idle или Loading. Всякий раз, когда DataLoadDialog получает вызов loadStart или loadComplete, он определяет, показывать или не показывать диалоговое окно хода выполнения, в зависимости от состояния потоков загрузки данных.

    Диалог процесса - это одноэлементный статический экземпляр, который создается при создании класса (из xml конфигурации).

Любая помощь приветствуется.Если я что-то пропустил, тогда, пожалуйста, добавьте комментарий, и я обновлю вопрос.

Спасибо,

Andez

Ответы [ 3 ]

3 голосов
/ 07 января 2012

Проблемы звучат как нарушение базового правила Swing: доступ ко всем компонентам должен происходить в EDT.Убедитесь, что вся обратная связь с пользователем (показ / скрытие диалога, обновление состояния процесса, обновление данных таблицы) активируется в EDT.

Полезным классом, помогающим сделать это, является SwingWorker, его API довольно обширный и содержит пример прямого доступа к моделям / компонентам.

Некоторые фрагменты псевдокода, показывающие, как немного отделить работника от реального пользовательского интерфейса путем передачи промежуточных данных через propertyChangeEvent:

// 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);
         } 
    }

}
2 голосов
/ 07 января 2012

В дополнение к убедительным предложениям @ kleopatra в статье цитируется здесь обсуждаются способы поиска нарушений EDT в существующем коде.Больше примеров здесь и здесь .

0 голосов
/ 12 января 2012

Я заглянул в SwingWorker. Я работал с примерами, но идея состояла в том, чтобы порождать каждый DataLoader из SwingWorker - я думаю.

Я решил не делать этого и обнаружил проблему с тем, как происходит взаимодействие между классом DataLoadDialog и Swing JDialog. Я слишком упростил свой пример. Были 2 дополнительные комбинации, которые также вызвали загрузку данных. Одна комбо заставит 3 загрузчика данных начать загрузку, а другая комбо для 3 других загрузчиков данных. Это заставило бы меня добавить дополнительную конфигурацию, что было бы неудобно из-за способа написания системы.

Сначала я попытался добавить синхронизированное ключевое слово в loadStart и loadComplete, которые все еще не решали проблему. У меня также был invokeAndWait в месте, где диалог был отображен / скрыт. Я надеялся, что это предотвратит попытки нескольких потоков показать / скрыть прогресс, но это не помогло. Я заменил invokeAndWait на invokeLater, который, похоже, добился цели.

Я полностью согласен с SwingWorker, подобные сценарии должны быть более продуманными, что нам здесь плохо, но я ненавижу это. Все параллельное решение было собрано после того, как потребовалось около 20 минут для синхронной загрузки исходных данных - 1 загрузчик по очереди извлекал данные из базы данных. Объем загружаемых данных довольно ужасен - и он также должен был пройти через какое-то условие.

Andez

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...