3.5.8.1. Background Task Usage Examples
- Display and control background task operation with BackgroundWorkProgressWindow
-
Often when launching a background task one needs to display a simple UI:
-
to show to the user that requested action is in the process of execution,
-
to allow user to abort requested long operation,
-
to show operation progress if progress percent can be determined.
Platform satisfies these needs with
BackgroundWorkWindow
andBackgroundWorkProgressWindow
utility classes. These classes have static methods allowing to associate background task with a modal window that has a title, description, progress bar and optionalCancel
button. The difference between two classes is thatBackgroundWorkProgressWindow
uses determinate progress bar, and it should be used in case if you can estimate progress of the task. Conversely,BackgroundWorkWindow
should be used for tasks of indeterminate duration.Consider the following development task as an example:
-
A given screen contains a table displaying a list of students, with multi-selection enabled.
-
When the user presses a button, the system should send reminder emails to selected students, without blocking UI and with an ability to cancel the operation.
Sample implementation:
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 - launch the task and show modal progress window 2 - set dialog options: total number of elements for progress bar, user can cancel a task, show progress percent 3 - task progress unit is Integer
(number of processed table items), and result type isVoid
because this task doesn’t produce result4 - selected table items are saved into a variable which is initialized in the task constructor. This is necessary because run()
method is executed in a background thread and cannot access UI components.5 - set timeout to 10 minutes 6 - periodically check isCancelled()
so that the task can stop immediately after the user pressedCancel
dialog button7 - update progress bar position after every email sent -
- Periodically refresh screen data in the background using Timer and BackgroundTaskWrapper
-
BackgroundTaskWrapper
is a tiny utility wrapper aroundBackgroundWorker
. It provides simple API for cases when background tasks of the same type get started, restarted and cancelled repetitively.As a usage example, consider the following development task:
-
A rank monitoring screen needs to display and automatically update some data.
-
Data is loaded slowly and therefore it should be loaded in the background.
-
Show time of the latest data update on the screen.
-
Data is filtered with simple filter (checkbox).
-
If data refresh fails for some reason, the screen should indicate this fact to the user:
Sample implementation:
@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 - initialize BackgroundTaskWrapper
instance with no-arg constructor; for every iteration a new task instance will be supplied2 - immediately trigger a background data refresh after checkbox value has changed 3 - every timer tick triggers a data refresh in the background 4 - task publishes no progress so progress unit is Void
; task produces result of typeList<Rank>
5 - checkbox state is saved into a variable which is initialized in the task constructor. This is necessary because run()
method is executed in a background thread and cannot access UI components.6 - call custom service to load data (this is the long operation to be executed in the background) 7 - apply successfully obtained result to screen’s components 8 - update UI in the special case if data loading timed out: show notification in the screen corner 9 - inform user that data loading has failed with exception by showing notification -