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