Background Tasks
The background tasks mechanism is designed to perform tasks asynchronously using the security context of the current user and updating the UI with the results.
In comparison to simpler Asynchronous Tasks, the background tasks API provides the following additional features:
-
Ability to visualize the progress of the operation.
-
Ability for the user to interrupt the operation.
-
A ready-made configurable UI dialog for displaying the information about the operation, a progress bar and a cancel button.
Basics
To use background tasks, do the following:
-
Define a task as a subclass of the
BackgroundTask
abstract class. In the constructor method for your task, pass a timeout value and the reference to the view controller associated with the task.Closing the view interrupts the tasks associated with it. Besides, the task is interrupted automatically after the specified timeout.
-
Implement the task in the
BackgroundTask.run()
method. -
Create an object of
BackgroundTaskHandler
class controlling the task by passing the task instance to thehandle()
method of theBackgroundWorker
bean. You can obtain a reference toBackgroundWorker
by injection in the view controller or viaApplicationContext
. -
Run the task by invoking the
execute()
method ofBackgroundTaskHandler
.
Do not read or update the state of UI components and data containers in BackgroundTask.run() method; use done() , progress() , and canceled() callback methods instead. If you try to set a value to a UI component from a background thread, IllegalConcurrentAccessException is thrown.
|
Below is an example of running a background task and tracking its progress using the progressBar component:
@ViewComponent
protected ProgressBar progressBar; (1)
@Autowired
protected BackgroundWorker backgroundWorker;
protected BackgroundTaskHandler<Void> taskHandler;
private static final int ITERATIONS = 6;
@Subscribe
protected void onInit(InitEvent event) { (2)
taskHandler = backgroundWorker.handle(createBackgroundTask());
taskHandler.execute();
}
protected BackgroundTask<Integer, Void> createBackgroundTask () { (3)
return new BackgroundTask<>(100, TimeUnit.SECONDS) {
@Override
public Void run(TaskLifeCycle<Integer> taskLifeCycle) throws Exception {
for (int i=1; i< ITERATIONS; i++) {
TimeUnit.SECONDS.sleep(1);
taskLifeCycle.publish(i);
}
return null;
}
@Override
public void progress (List<Integer> changes) {
double lastValue = changes.get(changes.size() - 1);
double value = lastValue/ITERATIONS;
progressBar.setValue(value); (4)
}
};
}
1 | A task that takes time to complete. The run() method is executed in a separate thread. |
2 | The progress() method is executed in the UI thread, so visual components can be updated here. |
BackgroundTask Class
BackgroundTask<T, V>
is a parameterized class:
-
T
− the type of objects displaying task progress. Objects of this type are passed to the task’sprogress()
method during an invocation ofTaskLifeCycle.publish()
in the working thread. -
V
− task result type passed to thedone()
method. You can also obtain the result by invokingBackgroundTaskHandler.getResult()
method, which will wait for a task to complete.
The BackgroundTask
class is thread-safe and does not define a state. If you don’t create fields for temporary data when implementing task class, you can start several parallel processes using a single task instance.
BackgroundTask Methods
Detailed information about methods is provided in Javadocs for BackgroundTask
, TaskLifeCycle
, BackgroundTaskHandler
classes.
run()
This method implements the task. It is invoked in a separate working thread to perform the task.
The method should support external interruptions. To ensure this, check the TaskLifeCycle.isInterrupted()
flag periodically during long processes and stop execution when needed. Additionally, you should not silently discard InterruptedException
(or any other exception) — instead, you should either exit the method correctly or not handle the exception at all.
You can also check whether the task was interrupted with cancel()
using the isCancelled()
method.
canceled()
This method is invoked in UI thread only during a controlled cancellation of a task, for example, when the cancel()
method is invoked in the TaskHandler
.
progress()
This method is invoked in the UI thread when the progress value changes. For example, after calling the taskLifeCycle.publish()
method.
Notes & Tips
-
Use
dialogs.createBackgroundTaskDialog()
method to show a modal window with a progress indicator and Cancel button. It lets you define progress indication type and allow or prohibit the cancellation of the background task for the window. -
If you need to use certain values of visual components in the task thread, you should implement their acquisition in the
getParams()
method, which runs in the UI thread once a task starts. In therun()
method, these parameters will be accessible via thegetParams()
method of theTaskLifeCycle
object. -
Background tasks are affected by jmix.ui.background-task.task-killing-latency, jmix.ui.background-task.threads-count, and jmix.ui.background-task.timeout-expiration-check-interval application properties.
Usage Example
Often, when launching a background task, you need to display a simple user interface:
-
To show to the user that the requested action is in the process of execution.
-
To allow the user to abort the requested long operation.
-
To show operation progress if progress percent can be determined.
You can do it using the createBackgroundTaskDialog()
method of the Dialogs interface.
To illustrate this, consider the following development task:
-
A view contains a data grid with a list of users, with multi-selection enabled.
-
On clicking Send email, the system sends reminder emails to selected users, without blocking UI and with a possibility to cancel the operation.
@ViewComponent
private DataGrid<User> usersTable;
@Autowired
private Emailer emailer;
@Autowired
private Dialogs dialogs;
@Subscribe("sendByEmail")
public void onSendByEmailClick(ClickEvent event) {
Set<User> selected = usersTable.getSelectedItems();
if (selected.isEmpty()) {
return;
}
BackgroundTask<Integer, Void> task = new EmailTask(selected);
dialogs.createBackgroundTaskDialog(task) (1)
.withHeader("Sending reminder emails")
.withText("Please wait while emails are being sent")
.withTotal(selected.size())
.withShowProgressInPercentage(true)
.withCancelAllowed(true)
.open();
}
private class EmailTask extends BackgroundTask<Integer, Void> { (2)
private Set<User> users; (3)
public EmailTask(Set<User> users) {
super(10, TimeUnit.MINUTES, BackgroundTasksView.this); (4)
this.users = users;
}
@Override
public Void run(TaskLifeCycle<Integer> taskLifeCycle) throws Exception {
int i = 0;
for (User user : users) {
if (taskLifeCycle.isCancelled()) { (5)
break;
}
EmailInfo emailInfo = EmailInfoBuilder.create() (6)
.setAddresses(user.getEmail())
.setSubject("Reminder")
.setBody("Your password expires in 14 days!")
.build();
emailer.sendEmail(emailInfo);
i++;
taskLifeCycle.publish(i); (7)
}
return null;
}
}
1 | Create the task, create progress dialog, and set its options:
|
2 | Task progress unit is Integer - the number of processed table items, and result type is Void because this task doesn’t return a result. |
3 | Selected table items are saved into a variable that is initialized in the task constructor. This is necessary because the run() method is executed in a background thread and cannot access UI components. |
4 | Set timeout to 10 minutes. |
5 | Periodically check isCancelled() to stop the task immediately after pressing the Cancel dialog button. |
6 | Send email. See more about email sending here. |
7 | Update progress bar completion after every email sent. |