3.5.8.1. Примеры использования фоновых задач
- Отображение выполнения и управление фоновой задачей с помощью BackgroundWorkProgressWindow
-
Часто при запуске фоновых задач появляется необходимость отображения простого UI:
-
показать пользователю, что запрошенное действие находится в процессе выполнения,
-
дать пользователю возможность прервать запрошенное долгое действие,
-
показать процент выполнения, если его можно определить.
Для реализации этих потребностей платформа предоставляет вспомогательные классы
BackgroundWorkWindow
иBackgroundWorkProgressWindow
. Эти классы содержат статические методы, позволяющие связать фоновую задачу с модальным диалогом, отображающим заголовок, описание, индикатор прогресса и возможно кнопкуОтмена
. Разница между этими двумя классами в том, чтоBackgroundWorkProgressWindow
использует определённый индикатор прогресса, и оно должно использоваться только если задача может оценить процент своего выполнения. КлассBackgroundWorkWindow
следует использовать для задач, где нельзя оценить прогресс выполнения.В качестве примера рассмотрим следующую задачу по разработке:
-
Некоторый экран содержит таблицу, отображающую список студентов, с включенным множественным выделением.
-
По нажатию кнопки система должна послать письма-напоминания выбранным студентам, без блокировки UI и с возможностью прервать действие.
Пример реализации:
import com.haulmont.cuba.gui.backgroundwork.BackgroundWorkProgressWindow; public class StudentBrowse extends StandardLookup<Student> { @Inject private Table<Student> studentsTable; @Inject private EmailService emailService; @Subscribe("studentsTable.sendEmail") public void onStudentsTableSendEmail(Action.ActionPerformedEvent event) { Set<Student> selected = studentsTable.getSelected(); if (selected.isEmpty()) { return; } BackgroundTask<Integer, Void> task = new EmailTask(selected); BackgroundWorkProgressWindow.show(task, (1) "Sending reminder emails", "Please wait while emails are being sent", selected.size(), true, true (2) ); } private class EmailTask extends BackgroundTask<Integer, Void> { (3) private Set<Student> students; (4) public EmailTask(Set<Student> students) { super(10, TimeUnit.MINUTES, StudentBrowse.this); (5) this.students = students; } @Override public Void run(TaskLifeCycle<Integer> taskLifeCycle) throws Exception { int i = 0; for (Student student : students) { if (taskLifeCycle.isCancelled()) { (6) break; } emailService.sendEmail(student.getEmail(), "Reminder", "Don't forget, the exam is tomorrow", EmailInfo.TEXT_CONTENT_TYPE); i++; taskLifeCycle.publish(i); (7) } return null; } } }
1 - запустить задачу и показать модальное окно с прогрессом 2 - установить опции диалога: "размер" индикатора прогресса, пользователь может прервать задачу, показывать прогресс в процентах 3 - прогресс задачи измеряется в Integer
(число обработанных элементов таблицы), а тип результата -Void
, потому что эта задача не производит результата4 - выбранные элементы таблицы сохраняются в переменную, которая инициализируется в конструкторе задачи. Это необходимо, потому что метод run()
исполняется в фоновом потоке и не может обращаться к UI компонентам.5 - установить таймаут равный 10 минутам 6 - периодически проверяется isCancelled()
, чтобы задача сразу завершилась после того, как пользователь нажмет кнопкуCancel
7 - обновить индикатор прогресса после каждого посланного письма -
- Периодическое фоновое обновление данных экрана с использованием Timer и BackgroundTaskWrapper
-
BackgroundTaskWrapper
- это вспомогательный класс, тонкая обертка вокругBackgroundWorker
. Он предоставляет простое API для случаев, когда фоновые задачи одного и того же вида запускаются, перезапускаются и отменяются много раз.В качестве примера использования рассмотрим следующую задачу по разработке:
-
Имеется экран мониторинга очередей, в котором нужно отображать и автоматически обновлять какие-то табличные данные.
-
Данные загружаются медленно, и поэтому их нужно загружать в фоне.
-
Нужно отображать на экране время последнего обновления.
-
Данные ограничены простым фильтром (флажок checkbox).
-
Если обновить данные по каким-то причинам не получилось, то экран должен оповестить об этом пользователя:
Пример реализации:
@UiController("playground_RankMonitor") @UiDescriptor("rank-monitor.xml") public class RankMonitor extends Screen { @Inject private Notifications notifications; @Inject private Label<String> refreshTimeLabel; @Inject private CollectionContainer<Rank> ranksDc; @Inject private RankService rankService; @Inject private CheckBox onlyActiveBox; @Inject private Logger log; @Inject private TimeSource timeSource; @Inject private Timer refreshTimer; private BackgroundTaskWrapper<Void, List<Rank>> refreshTaskWrapper = new BackgroundTaskWrapper<>(); (1) @Subscribe public void onBeforeShow(BeforeShowEvent event) { refreshTimer.setDelay(5000); refreshTimer.setRepeating(true); refreshTimer.start(); } @Subscribe("onlyActiveBox") public void onOnlyActiveBoxValueChange(HasValue.ValueChangeEvent<Boolean> event) { refreshTaskWrapper.restart(new RefreshScreenTask()); (2) } @Subscribe("refreshTimer") public void onRefreshTimerTimerAction(Timer.TimerActionEvent event) { refreshTaskWrapper.restart(new RefreshScreenTask()); (3) } public class RefreshScreenTask extends BackgroundTask<Void, List<Rank>> { (4) private boolean onlyActive; (5) protected RefreshScreenTask() { super(30, TimeUnit.SECONDS, RankMonitor.this); onlyActive = onlyActiveBox.getValue(); } @Override public List<Rank> run(TaskLifeCycle<Void> taskLifeCycle) throws Exception { List<Rank> data = rankService.loadActiveRanks(onlyActive); (6) return data; } @Override public void done(List<Rank> result) { (7) List<Rank> mutableItems = ranksDc.getMutableItems(); mutableItems.clear(); mutableItems.addAll(result); String hhmmss = new SimpleDateFormat("HH:mm:ss").format(timeSource.currentTimestamp()); refreshTimeLabel.setValue("Last time refreshed: " + hhmmss); } @Override public boolean handleTimeoutException() { (8) displayRefreshProblem(); return true; } @Override public boolean handleException(Exception ex) { (9) log.debug("Auto-refresh error", ex); displayRefreshProblem(); return true; } private void displayRefreshProblem() { if (!refreshTimeLabel.getValue().endsWith("(outdated)")) { refreshTimeLabel.setValue(refreshTimeLabel.getValue() + " (outdated)"); } notifications.create(Notifications.NotificationType.TRAY) .withCaption("Problem refreshing data") .withHideDelayMs(10_000) .show(); } } }
1 - создать экземпляр BackgroundTaskWrapper
через конструктор без параметров; для каждой итерации будет передан новый экземпляр задачи2 - немедленно запустить фоновое обновление данных после смены состояния флажка 3 - каждое срабатывание таймера запускает фоновое обновление данных 4 - задача не публикует ход прогресса, поэтому тип прогресса Void
; задача производит результат с типомList<Rank>
5 - состояние флажка сохраняется в переменную, которая инициализируется в конструкторе задачи. Это необходимо, потому что метод run()
выполняется в фоновом потоке и не может обращаться к UI компонентам.6 - вызов пользовательского сервиса для загрузки данных (это долгое действие и исполняется в фоновом потоке) 7 - применить успешно полученный результат к компонентам экрана 8 - обновить UI в особом случае, если загрузка данных не выполнилась за время таймаута: показать уведомление в углу экрана 9 - проинформировать пользователя, показав уведомление, если загрузка данных завершилась исключением -