Vaadin Grid异步内容加载优化:实现平滑渐进式渲染
论文探讨了Vaadin Grid在集成异步数据加载时可能遇到的“同步”加载问题,即实现了Push并使用了解决异步方法,网格内容仍然批量加载,而不是逐项逐步进显示。核心方案是通过在单独的线程中启动每个项目的异步操作,确保UI在数据准备好之前能立即渲染占位符,从而显着提升用户体验界面和响应性。Vaadin网格异步加载的挑战
在使用vaadin构建web应用时,组件grid是展示大量数据的常用选择。为了提供流畅的用户体验,特别是当数据需要运行操作(如网络请求、复杂计算)才能获取时,异步加载队列队列。vaadin的推送机制允许服务器端主动向客户端发送请求动态更新,这为异步数据加载提供了基础。然而,开发者经常遇到的一个问题是,即使实现了推送并使用了异步方法(例如spring的@async),网格的内容(特别是通过addcomponentcolumn添加的自定义组件)仍然表现出“同步”加载的特性——即整个网格的ui结构会立即显示,但内部的组件内容却需要等待所有异步操作完成后才批量填充,导致一段明显的空白期。
问题的根源在于,尽管异步方法本身在后台线程执行操作,但触发这些异步操作的调用可能仍然发生在 Vaadin 的 UI 线程中。如果这些调用(即使是返回Listena) bleFuture的调用)在UI渲染周期中阻塞了UI线程,或者其返回的Future在UI线程中被“等待”以进行后续处理,那么用户仍然会遇到延迟。对于像Image这样的组件,如果其src属性在初始渲染时无法立即获取,希望它能先我们以空状态完成,待数据加载显示后再更新。
最初尝试与约束
考虑以下Vaadin Grid的设置,它尝试异步加载每个单元格的图标:// Grid设置private void setupGrid() { Gridlt;Stringgt; grid = new Gridlt;gt;(); // 为每个网格项添加一个组件列,该组件将异步加载图标 grid.addComponentColumn(this::getIconColumn).setHeader(quot;Namequot;); grid.setItems(quot;item1quot;, quot;item2quot;, quot;item3quot;); // 启用Vaadin Push更新,允许服务器异步自适应UI变更 grid.getDataCommunicator().enablePushUpdates(Executors.newCachedThreadPool()); add(grid);}//获取图标列的组件方法 private Image getIconColumn(String entityName) { Image image = new Image(quot;quot;, quot;quot;); // 最初为空图片 image.setHeight(quot;150pxquot;); image.setWidth(quot;100pxquot;); UI ui = UI.getCurrent(); // 获取当前UI实例,用于线程安全更新 // 异步加载图标,并设置回调 //即使asyncLoadIcon是@Async方法,此处的调用仍可能在UI线程中触发, // 导致UI在Future完成前无法渲染空图片 asyncLoadIcon(entityName) .addCallback( result -gt; ui.access(() -gt; image.setSrc(result)), // 成功时更新图片源 err -gt; ui.access(() -gt; notification.show(quot;Failed to load icon for quot;entityName)) // 失败时显示通知 ); return image; // 返回图片组件}// 模拟异步加载图标的方法@Async // Spring 的异步注解 public ListenableFuturelt;Stringgt; asyncLoadIcon(字符串实体名称){尝试{ Thread.sleep(3000); // 模拟耗时操作 return AsyncResult.forValue(quot;path/to/icon/quot; entityName quot;.pngquot;); } catch (InterruptedException e) { Thread.currentThread().interrupt(); return As
yncResult.forExecutionException(new RuntimeException(quot;图标加载中断quot;)); }}登录后复制
虽然asyncLoadIcon方法被标记为@Async,其内部的运行操作会在单独的线程中执行,但getIconColumn方法在Vaadin UI渲染过程中被调用。在这种情况下,asyncLoadIcon(entityName)的本身,以及ListenableFuture的和创建回调调用的注册,可能仍然在UI线程中发生。如果这个过程相对同步(即使是微秒级,但对于大量数据项积累起来就明显了),或者Vaad在渲染机制中等待Future的“设置”完成,那么UI仍然会感觉被阻塞,直到所有这些Future都被创建并注册完毕。结果是,网格的解决只是显示了,但所有的图片内容都延迟了3秒后才一次性出现,而不是逐个填充。方案:在独立线程中启动异步任务
才能实现真正的的渐进式加载,关键在于确保在 getIconColumn 方法返回Image组件时,该组件能够立即被添加到网格并渲染出来,而消耗等待任何异步操作的启动。这意味着,即使是异步操作的启动也应该在非 UI 线程中发生。
解决方案是在 getIconColumn 方法内部,为每个网格项的异步图标加载任务创建一个新的线程并启动它。这样,getIconColumn方法可以快速返回一个空的Image组件,Vaadin可以立即渲染它。当后台线程中的异步任务立即完成任务时,通过UI.access()方法安全地更新UI。 Image getIconColumn(MangaEntityEntity) { Image image = new Image(quot;quot;, quot;quot;); // 首先为空图片,立即返回 image.setHeight(quot;150pxquot;); image.setWidth(quot;100pxquot;); UI ui = UI.getCurrent(); // 获取当前UI实例 // 在一个新的线程中启动异步加载任务 //解决,getIconColumn方法可以立即返回不会,阻止UI渲染 new Thread(() -gt; { // 调用异步加载图标的方法 // 注意:这里的rsCrawler.asyncLoadIcon假设是一个Spring Bean, // 它的@Async注解会确保实际的运行操作在Spring的线程池中执行。 // new Thread()确保了对rsCrawler.asyncLoadIcon的调用本身不阻塞UI线程。
rsCrawler.asyncLoadIcon(entity.getUrlName()) .addCallback( // 成功回调:使用ui.access()安全地更新UI result -gt; ui.access(() -gt; image.setSrc(result)), // 失败回调:使用显示ui.access()安全地通知 err -gt; ui.access(() -gt; notification.show(quot;无法解析 quot 图标; entity.getName())) ); }).start(); // 启动新线程 return image; // 立即返回Image组件,允许Vaadin立即渲染占位符}登录后复制
通过这种方式,当Grid渲染时,getIconColumn方法会非常迅速地返回一个Image实例。Vaadin能够立即将该空的组件添加到Grid中并出来显示。同时,一个新的线程被启动,它负责调用asyncLoad Icon并处理回调。当asyncLoadIcon完成其运行操作后,它会通过addCallback触发UI.access()来更新对应的Image组件的src属性。这样,用户会立即看到网格结构和空的图片占位符出现,然后图片会逐个、逐步地填充进来,极大地提升了用户体验。注意事项与最佳实践
线程管理:尽管new Thread(() -gt; ...).start()可以解决问题,但在生产环境中创建新线程并不是最佳实践,因为它会带来额外的开销和资源管理问题。更推荐的做法是使用ExecutorService(如ThreadPoolExecutor或Executors.newCachedThreadPool())来管理和复用线程。Spring的@Async注解背后通常就是由一个ThreadPoolTaskExecutor支持的,所以上述解决方案中,new Thread()的作用是确保rsCrawler.asyncLoadIcon的调用不阻塞UI线程,而@Async确保asyncLoadIcon内部的实际运行操作在线程池中执行。
处理错误:确保在异步回调中处理并发异常。addCallback的第二个参数用于处理错误,并通过UI.access()安全地向用户显示错误信息。
用户体验:这种渐进式加载方式显着提升了用户对应用响应速度的认知。对于图片等媒体内容,可以在加载期间显示加载(例如Spinner)或占位符图片,以进一步优化用户体验。
取消机制: 对于长时间运行的异步任务,如果用户在任务完成前离开视图,考虑实现任务取消机制,占用不必要的资源消耗和潜在的内存浪费。总结
在Vaadin Grid中实现真正的异步渐进式内容加载,关键在于将运行操作的启动也从UI线程中分离出来。
虽然瓦丁Push和UI.access()保证了UI更新的线程安全,但如果异步任务的初始化本身阻塞了UI渲染,用户体验仍然会陷入困境。通过在单独的线程中启动每个项目的异步任务,我们可以保证Grid能够立即渲染占位符,并且再逐项填充,从而提供一个更加时序和响应迅速的用户界面。在实际项目中,应结合线程池等更高级的内容的线程管理策略,以保证应用的性能和稳定性。
以上就是Vaadin网格异步内容加载优化:实现平滑渐进式渲染的详细内容,更多请关注乐哥常识网其他相关文章!