Background Tasks

Background tasks mechanism is designed for performing tasks asynchronously without blocking the user interface.

Basics

To use background tasks, do the following:

  1. 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.

  2. Implement the task in the BackgroundTask.run() method.

  3. Create an object of BackgroundTaskHandler class controlling the task by passing the task instance to the handle() method of the BackgroundWorker bean. You can obtain a reference to BackgroundWorker by injection in the screen controller or via ApplicationContext.

  4. Run the task by invoking the execute() method of BackgroundTaskHandler.

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:

  1. T − the type of objects displaying task progress. Objects of this type are passed to the task’s progress() method during an invocation of TaskLifeCycle.publish() in the working thread.

  2. V − task result type passed to the done() method. You can also obtain the result by invoking BackgroundTaskHandler.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.

done()

This method is invoked in UI thread when the task is completed.

handleTimeoutException()

This method is invoked in UI thread when the task timeout expires. If the window where the task is running closes, the task is stopped without notification.

handleException()

This method is invoked in UI thread when any exceptions occur.

Notes & Tips

  1. 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.

  2. 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 the run() method, these parameters will be accessible via the getParams() method of the TaskLifeCycle object.

  3. Background tasks are affected by jmix.ui.backgroundTaskTimeoutCheckInterval and jmix.ui.background-task.threadsCount application properties.

Usage Example

Often, when launching a background task, you need to display a simple user interface:

  1. To show to the user that the requested action is in the process of execution.

  2. To allow the user to abort the requested long operation.

  3. 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:

  1. A given screen contains a table displaying a list of customers, with multi-selection enabled.

  2. 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.

emails

@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:
  • dialog caption;

  • dialog message;

  • total number of elements for progress bar;

  • show progress in percent or not;

  • show Cancel button or not.

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.