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

  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 view 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:

@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:

  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.

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.

done()

This method is invoked in UI thread once the task is complete.

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

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

  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 createBackgroundTaskDialog() method of the Dialogs interface.

To illustrate this, consider the following development task:

  1. A view contains a data grid with a list of users, with multi-selection enabled.

  2. On clicking Send email, the system sends reminder emails to selected users, without blocking UI and with a possibility to cancel the operation.

emails

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

  • dialog text;

  • 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 completion after every email sent.