后台任务
后台任务机制支持异步执行任务,不阻塞用户界面。
基本用法
如需使用后台任务,请按照下列步骤:
- 
定义一个继承自 BackgroundTask抽象类的任务。任务类的构造函数接收两个参数,一个是任务关联的界面控制器,另一个是用于任务构造函数的超时时限。关闭界面将中断与其相关的后台任务。此外,任务将在指定的时限超时后自动中断。 
- 
在 BackgroundTask.run()方法中实现任务逻辑。
- 
通过将任务实例传递给 BackgroundWorkerbean 的handle()方法,可以创建一个BackgroundTaskHandler类对象用于对任务进行控制。在界面控制器中注入或通过ApplicationContext类来获得对BackgroundWorker的引用。
- 
调用 BackgroundTaskHandler的execute()方法启动任务。
| 不要在 BackgroundTask.run()方法中读取或更新 UI 组件的状态和数据容器,而使用done()、progress()和canceled()回调方法替代。如果尝试从后台线程设置 UI 组件的状态,则会抛出IllegalConcurrentAccessException。 | 
下面示例中,运行一个后台任务并通过 ProgressBar 组件跟踪任务进度:
@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 | 需要一些时间才能完成的任务。 run()方法在单独线程中执行。 | 
| 2 | progress()方法在 UI 线程中执行,因此我们可以在这里更新可视化组件状态。 | 
BackgroundTask 类
BackgroundTask<T, V> 是一个参数类:
- 
T- 显示任务进度的对象类型。在工作线程调用TaskLifeCycle.publish()时,将此类型对象传递给任务的progress()方法。
- 
V- 传递给done()方法的任务结果类型。结果也可以调用BackgroundTaskHandler.getResult()方法获得,该方法将等待至任务完成。
BackgroundTask 对象是无状态的。如果在实现任务类时没有为临时数据创建类变量,则可以使用单个任务实例启动多个并行进程。
BackgroundTask 类方法
JavaDocs 中提供了 BackgroundTask、TaskLifeCycle、BackgroundTaskHandler 类的有关方法的详细信息。
注意事项
- 
用 dialogs.createBackgroundWorkDialog()方法展示包含进度条和 Cancel 按钮的模态窗。该模态窗可以定义进度条的不同类型,以及允许或禁止取消后台任务。
- 
如果需要在任务线程中使用可视化组件的某个值,应该在 getParams()方法中实现获取的逻辑,该方法在任务启动时在 UI 线程中运行一次。在run()方法中,可以通过TaskLifeCycle对象的getParams()方法访问这些参数。
- 
后台任务受 jmix.ui.background-task-timeout-check-interval 和 jmix.ui.background-task.threads-count 应用程序属性的影响。 
用例
通常,在启动后台任务时,需要展示一个简单的用户界面:
- 
为用户展示请求的操作正在执行中。 
- 
允许用户中止长时间的操作。 
- 
如果可以确定执行的百分比,则显示操作的进度。 
可以用 Dialogs 接口的 createBackgroundWorkDialog() 方法完成。
考虑下面情形作为示例:
- 
给定的界面包含展示 customer 的表格,支持多选。 
- 
当用户点击按钮时,系统给选定的 customer 发送提醒邮件,发邮件的过程不阻塞 UI,并支持中止操作。 
@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 | 启动任务,展示进度对话框,并设置: 
 | 
| 2 | 任务进度单位是 Integer- 处理的表格条目数量,结果类型是Void,因为任务不会返回结果。 | 
| 3 | 选中的表格条目保存在一个变量中,会在任务的构造函数中初始化。必须如此实现,因为 run()会在后台线程中执行,无法访问 UI 组件。 | 
| 4 | 设置超时时限为 10 分钟。 | 
| 5 | 周期性的检查 isCancelled(),以便在用户点击 Cancel 按钮时立即停止任务。 | 
| 6 | 发送邮件。参阅 文档 了解关于发送邮件的更多内容。 | 
| 7 | 每个邮件发送后,更新进度条位置。 |