You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用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,可采用后端主动推送的方式,性能更优且无调用重叠问题:

  1. 在faces-config.xml中配置WebSocket资源处理器:
<application>
  <resource-handler>org.primefaces.push.PushResourceHandler</resource-handler>
</application>
  1. 定义推送通道端点:
@PushEndpoint("/taskMessages")
public class TaskMessageEndpoint {
  @OnMessage(encoders = {JSONEncoder.class})
  public String onMessage(String message) {
    return message;
  }
}
  1. 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);
    }
  }
}
  1. 页面接收推送并更新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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.05 08:30:22