Asynchronous Updates
Environment.runLater() API 提供了一种机制,可以安全地从 webforJ 应用程序中的后台线程更新 UI。此实验特性能够实现异步操作,同时保持 UI 修改的线程安全性。
The webforj-handling-timers-and-async skill can schedule timers, debouncers, and async work safely on the UI thread. After installing the webforJ AI plugin, ask your assistant:
- "Refresh this dashboard every 30 seconds."
- "Add a search-as-you-type debouncer."
- "Run this CPU-heavy work in the background and update the progress bar."
理解线程模型
webforJ 强制执行严格的线程模型,所有 UI 操作必须在 Environment 线程上进行。此限制存在的原因:
- webforJ API 限制:底层 webforJ API 绑定到创建会话的线程
- 组件线程亲和性:UI 组件维护的状态不是线程安全的
- 事件调度:所有 UI 事件都是在单线程上按顺序处理的
这种单线程模型防止了竞争条件,并维护了所有 UI 组件的一致状态,但在与异步、长时间计算任务集成时会带来挑战。
RunLater API
Environment.runLater() API 提供了两种调度 UI 更新的方法:
// 调度一个没有返回值的任务
public static PendingResult<Void> runLater(Runnable task)
// 调度一个返回值的任务
public static <T> PendingResult<T> runLater(Supplier<T> supplier)
这两种方法都返回一个 PendingResult,它跟踪任务完成情况,并提供对结果或发生的任何异常的访问。
线程上下文继承
自动上下文继承是 Environment.runLater() 的一个关键特性。当在 Environment 中运行的线程创建子线程时,这些子线程会自动 继承使用 runLater() 的能力。
继承如何工作
从 Environment 线程内部创建的任何线程自动获得对该 Environment 的访问权限。这种继承是自动发生的,因此您无需传递任何上下文或配置任何内容。
@Route
public class DataView extends Composite<Div> {
private final ExecutorService executor = Executors.newCachedThreadPool();
public DataView() {
// 此线程具有 Environment 上下文
// 子线程自动继承上下文
executor.submit(() -> {
String data = fetchRemoteData();
// 可以使用 runLater,因为上下文被继承
Environment.runLater(() -> {
dataLabel.setText(data);
loadingSpinner.setVisible(false);
});
});
}
}
没有上下文的线程
在 Environment 上下文之外创建的 线程无法使用 runLater(),会抛出 IllegalStateException:
// 静态初始化器 - 没有 Environment 上下文
static {
new Thread(() -> {
Environment.runLater(() -> {}); // 抛出 IllegalStateException
}).start();
}
// 系统定时器线程 - 没有 Environment 上下文
Timer timer = new Timer();
timer.schedule(new TimerTask() {
public void run() {
Environment.runLater(() -> {}); // 抛出 IllegalStateException
}
}, 1000);
// 外部库线程 - 没有 Environment 上下文
httpClient.sendAsync(request, responseHandler)
.thenAccept(response -> {
Environment.runLater(() -> {}); // 抛出 IllegalStateException
});
执行行为
runLater() 的执行行为取决于调用它的线程:
从 UI 线程
当从 Environment 线程本身调用时,任务是 同步并立即 执行的:
button.onClick(e -> {
System.out.println("在之前: " + Thread.currentThread().getName());
PendingResult<String> result = Environment.runLater(() -> {
System.out.println("在内部: " + Thread.currentThread().getName());
return "已完成";
});
System.out.println("在之后: " + result.isDone()); // true
});
由于这种同步行为,事件处理程序中的 UI 更新会立即应用,而不需要任何不必要的排队开销。
从后台线程
当从后台线程调用时,任务会被 排队以进行异步执行:
@Override
public void onDidCreate() {
CompletableFuture.runAsync(() -> {
// 这在 ForkJoinPool 线程上运行
System.out.println("后台: " + Thread.currentThread().getName());
PendingResult<Void> result = Environment.runLater(() -> {
// 这在 Environment 线程上运行
System.out.println("UI 更新: " + Thread.currentThread().getName());
statusLabel.setText("处理完成");
});
// result.isDone() 在这里是 false
// 任务被排队,将异步执行
});
}
webforJ 从后台线程提交的任务以 严格的 FIFO 顺序 处理,即使是从多个线程同时提交也能保持操作顺序的正确性。由于这一排序保证,UI 更新按照提交的确切顺序应用。所以如果线程 A 提交任务 1,然后线程 B 提交任务 2,任务 1 始终会在 UI 线程上执行在任务 2 之前。按 FIFO 顺序处理任务可以防止 UI 中出现不一致性。