Background Tasks
Background tasks mechanism is designed for performing tasks asynchronously without blocking the user interface.
Basics
To use background tasks, do the following:
-
Define a task as a subclass of the
BackgroundTask
abstract class. Pass the reference to a screen controller associated with the task and the task timeout to the task constructor.Closing the screen will interrupt the tasks associated with it. Additionally, the task will be 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 screen 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:
@Autowired
protected ProgressBar progressBar;
@Autowired
protected BackgroundWorker backgroundWorker;
private static final int ITERATIONS = 6;
@Subscribe
protected void onInit(InitEvent event) {
BackgroundTask<Integer, Void> task = new BackgroundTask<Integer, Void>(100) {
@Override
public Void run(TaskLifeCycle<Integer> taskLifeCycle) throws Exception {
for (int i = 1; i <= ITERATIONS; i++) {
TimeUnit.SECONDS.sleep(1); (1)
taskLifeCycle.publish(i);
}
return null;
}
@Override
public void progress(List<Integer> changes) {
double lastValue = changes.get(changes.size() - 1);
progressBar.setValue(lastValue / ITERATIONS); (2)
}
};
BackgroundTaskHandler taskHandler = backgroundWorker.handle(task);
taskHandler.execute();
}
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 we can update visual components 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.
BackgroundTask
objects are stateless. 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 is invoked in a separate working thread to perform a 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 by the cancel()
method or not, 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 UI thread when the progress value change. For example, after calling the taskLifeCycle.publish()
method.
Notes & Tips
-
Use
dialogs.createBackgroundWorkDialog()
method to show a modal window with a progress indicator and Cancel button. You can 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-timeout-check-interval and jmix.ui.background-task.threads-count 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 createBackgroundWorkDialog()
method of the Dialogs interface.
Consider the following development task as an example:
-
A given screen contains a table displaying a list of customers, with multi-selection enabled.
-
When the user presses a button, the system should send reminder emails to selected customers, without blocking UI and with the ability to cancel the operation.
@Autowired
private Table<Customer> customersTable;
@Autowired
private Emailer emailer;
@Autowired
private Dialogs dialogs;
@Subscribe("sendByEmail")
public void onSendByEmailClick(Button.ClickEvent event) {
Set<Customer> selected = customersTable.getSelected();
if (selected.isEmpty()) {
return;
}
BackgroundTask<Integer, Void> task = new EmailTask(selected);
dialogs.createBackgroundWorkDialog(this, task) (1)
.withCaption("Sending reminder emails")
.withMessage("Please wait while emails are being sent")
.withTotal(selected.size())
.withShowProgressInPercentage(true)
.withCancelAllowed(true)
.show();
}
private class EmailTask extends BackgroundTask<Integer, Void> { (2)
private Set<Customer> customers; (3)
public EmailTask(Set<Customer> customers) {
super(10, TimeUnit.MINUTES, BackgroundTasksScreen.this); (4)
this.customers = customers;
}
@Override
public Void run(TaskLifeCycle<Integer> taskLifeCycle) throws Exception {
int i = 0;
for (Customer customer : customers) {
if (taskLifeCycle.isCancelled()) { (5)
break;
}
EmailInfo emailInfo = EmailInfoBuilder.create() (6)
.setAddresses(customer.getEmail())
.setSubject("Reminder")
.setBody("Your password expires in 14 days!")
.build();
emailer.sendEmail(emailInfo);
i++;
taskLifeCycle.publish(i); (7)
}
return null;
}
}
1 | Launch the task, show 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 position after every email sent. |