后台任务
后台任务机制支持异步执行任务,不阻塞用户界面。
基本用法
如需使用后台任务,请按照下列步骤:
-
定义一个继承自
BackgroundTask
抽象类的任务。任务类的构造函数接收两个参数,一个是任务关联的界面控制器,另一个是用于任务构造函数的超时时限。关闭界面将中断与其相关的后台任务。此外,任务将在指定的时限超时后自动中断。
-
在
BackgroundTask.run()
方法中实现任务逻辑。 -
通过将任务实例传递给
BackgroundWorker
bean 的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 | 每个邮件发送后,更新进度条位置。 |