Asynchronous Updates
Environment.runLater() API提供了一种机制,允许在webforJ应用程序中安全地从后台线程更新UI。这个实验性功能在实现异步操作的同时,保持了UI修改的线程安全性。
理解线程模型
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不一致。
任务取消
由Environment.runLater()返回的 PendingResult 支持取消,允许您防止排队的任务执行。通过取消待处理任务,您可以避免内存泄漏并防止不再需要的长期操作更新UI。
基本取消
PendingResult<Void> result = Environment.runLater(() -> {
updateUI();
});
// 如果尚未执行则取消
if (!result.isDone()) {
result.cancel();
}
管理多个更新
在执行频繁UI更新的长期操作时,追踪所有待处理结果:
public class LongRunningTask {
private final List<PendingResult<?>> pendingUpdates = new ArrayList<>();
private volatile boolean isCancelled = false;
public void startTask() {
CompletableFuture.runAsync(() -> {
for (int i = 0; i <= 100; i++) {
if (isCancelled) return;
final int progress = i;
PendingResult<Void> update = Environment.runLater(() -> {
progressBar.setValue(progress);
});
// 跟踪以便可能取消
pendingUpdates.add(update);
Thread.sleep(100);
}
});
}
public void cancelTask() {
isCancelled = true;
// 取消所有待处理的UI更新
for (PendingResult<?> pending : pendingUpdates) {
if (!pending.isDone()) {
pending.cancel();
}
}
pendingUpdates.clear();
}
}
组件生命周期管理
当组件被销毁(例如,在导航期间)时,取消所有待处理的更新以防止内存泄漏:
@Route
public class CleanupView extends Composite<Div> {
private final List<PendingResult<?>> pendingUpdates = new ArrayList<>();
@Override
protected void onDestroy() {
super.onDestroy();
// 取消所有待处理更新以防止内存泄漏
for (PendingResult<?> pending : pendingUpdates) {
if (!pending.isDone()) {
pending.cancel();
}
}
pendingUpdates.clear();
}
}
设计注意事项
-
上下文要求:线程必须继承一个
Environment上下文。外部库线程、系统定时器 和静态初始化器不能使用此API。 -
内存泄漏防止:始终在组件生命周期方法中跟踪并取消
PendingResult对象。排队的lambda表达式捕获对UI组件的引用,如果不被取消,将防止垃圾回收。 -
FIFO执行:所有任务都严格按照FIFO顺序执行,而不考虑重要性。没有优先级系统。
-
取消限制:取消仅防止排队任务的执行。已经执行的任务将正常完成。
完整案例研究:LongTaskView
以下是一个完整的生产就绪实现,展示了异步UI更新的所有最佳实践:
案例研究分析
这个实现展示了一些关键模式:
1. 线程池管理
private final ExecutorService executor = Executors.newSingleThreadExecutor(r -> {
Thread t = new Thread(r, "LongTaskView-Worker");
t.setDaemon(true);
return t;
});
- 使用单线程执行器以防止资源耗尽
- 创建