如何用Poll组件展示长耗时任务的进度及详细信息?
问题
我的Bean里有一个长耗时任务,目前已经能用progressBar正常展示进度,但希望额外通过dataTable这类组件实时显示生成的进度消息列表。但受Primefaces/JSF特性限制,绑定到dataTable的Bean变量新增内容只有在方法执行完毕后才会显示,而progressBar因为是通过AJAX轮询后台Bean可以实时更新进度。
我尝试把长任务longTask()(本质是循环逻辑)拆成执行单次迭代的executeNextTask(),并用Primefaces的poll组件调用,代码如下:
<p:poll widgetVar="myPoll" interval="1" autoStart="false" listener="#{myBean.executeNextTask()}" update="myDataTable"/> <p:commandButton value="Start process" onclick="PF('myPoll').start();"/> <p:dataTable value="#{myBean.messageList}"> ... </p:dataTable>
List<String> messageList; void executeNextTask() { if (!this.myObjects.isEmpty()) { Object o=this.myObjects.remove(0); // 执行任务逻辑 messageList.add("Processed object: "+o.toString()); } else { Primefaces.current().executeScript("PF('myPoll').stop()"); } }
这个方案能运行,但当executeNextTask()的执行时间超过poll的interval时,后续调用会出现重叠,不符合预期。请问有没有办法实现需求,可基于poll组件或其他组件/技术?
解决方案
方法1:给Poll组件加执行锁,避免重叠调用
在Bean中新增一个标记位,判断当前是否正在执行任务,仅当无任务执行时才处理轮询请求:
private boolean isTaskRunning = false; List<String> messageList; void executeNextTask() { if (isTaskRunning || this.myObjects.isEmpty()) { return; } try { isTaskRunning = true; Object o = this.myObjects.remove(0); // 执行任务逻辑 messageList.add("Processed object: "+o.toString()); if (this.myObjects.isEmpty()) { Primefaces.current().executeScript("PF('myPoll').stop()"); } } finally { isTaskRunning = false; } }
同时可适当调大poll的interval值(比如2-3秒),降低触发频率,进一步减少重叠概率。
方法2:用RemoteCommand替代Poll,实现“完成即触发”逻辑
这种方式从根源避免重叠,只有当前任务执行完成后,才主动发起下一次请求:
页面代码:
<p:remoteCommand name="executeNextTask" actionListener="#{myBean.executeNextTask}" update="myDataTable" oncomplete="checkAndRunNext()"/> <p:commandButton value="Start process" onclick="executeNextTask()"/> <p:dataTable value="#{myBean.messageList}"> ... </p:dataTable> <script> function checkAndRunNext() { if (#{not empty myBean.myObjects}) { executeNextTask(); } } </script>
Bean代码:
List<String> messageList; // 提供myObjects的getter方法,供前端判断 public List<Object> getMyObjects() { return this.myObjects; } void executeNextTask() { if (!this.myObjects.isEmpty()) { Object o = this.myObjects.remove(0); // 执行任务逻辑 messageList.add("Processed object: "+o.toString()); } }
方法3:使用异步任务+WebSocket(PrimeFaces 7+支持)
若PrimeFaces版本支持WebSocket,可采用后端主动推送的方式,性能更优且无调用重叠问题:
- 在
faces-config.xml中配置WebSocket资源处理器:
<application> <resource-handler>org.primefaces.push.PushResourceHandler</resource-handler> </application>
- 定义推送通道端点:
@PushEndpoint("/taskMessages") public class TaskMessageEndpoint { @OnMessage(encoders = {JSONEncoder.class}) public String onMessage(String message) { return message; } }
- Bean中用异步线程处理长任务,完成一次迭代就推送消息:
import org.primefaces.push.PushContext; import org.primefaces.push.PushContextFactory; import java.util.ArrayList; import java.util.List; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @ManagedBean @ViewScoped public class MyBean { private List<String> messageList = new ArrayList<>(); private List<Object> myObjects = new ArrayList<>(); private ExecutorService executor = Executors.newSingleThreadExecutor(); public void startLongTask() { executor.submit(() -> { while (!myObjects.isEmpty()) { Object o = myObjects.remove(0); // 执行任务逻辑 String message = "Processed object: "+o.toString(); // 线程安全更新本地列表 synchronized (messageList) { messageList.add(message); } // 推送消息到前端 PushContext pushContext = PushContextFactory.getDefault().getPushContext(); pushContext.push("/taskMessages", message); } }); } public List<String> getMessageList() { synchronized (messageList) { return new ArrayList<>(messageList); } } }
- 页面接收推送并更新dataTable:
<p:commandButton value="Start process" action="#{myBean.startLongTask()}"/> <p:dataTable id="myDataTable" widgetVar="myDataTableWidget" value="#{myBean.messageList}"> ... </p:dataTable> <p:socket channel="/taskMessages" onMessage="handleMessage"/> <script> function handleMessage(message) { PF('myDataTableWidget').refresh(); } </script>
内容的提问来源于stack exchange,提问作者jesjimher
相关产品推荐
相关产品推荐

