首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 更多程式語言 >> 程式設計綜合問答

Servlet 長時間任務瀏覽器關閉頁面無法停止後台執行緒:需引入取消機制

     編輯:程式設計綜合問答

瀏覽器關閉頁面/刷新/跳轉,只會切斷 HTTP 連接,不會自動終止 Servlet 線程中正在運行的長時間任務(如死循環、大數據導出、遠程調用)。Servlet 線程由容器管理,請求結束不代表線程結束。

原因分析

  • HTTP 是無狀態請求-響應模型,客戶端發起請求後斷開連接,服務端線程繼續執行直到代碼邏輯結束或拋出異常。
  • 容器(Tomcat/Jetty)不會因客戶端斷開而主動中斷線程(除非配置超時或線程池回收)。

解決方案:協作式取消模式

  1. 任務標識與狀態存儲:用 Redis/數據庫存儲任務 ID、狀態(RUNNING/CANCELLED)、進度。
  2. 定期檢查取消標誌:長任務循環中每 N 次迭代檢查標誌,若為取消則拋出異常/返回。
  3. 前端發起取消請求:頁面 beforeunload 發送異步請求(navigator.sendBeacon 或 fetch(..., {keepalive:true}))通知後端取消。

代碼示例

// 後端任務執行器
public class ExportTask implements Runnable {
    private String taskId;
    private volatile boolean cancelled = false;
    
    public void run() {
        try {
            for (int i = 0; i < totalRows; i++) {
                if (cancelled) throw new TaskCancelledException();
                // 處理第 i 行...
                if (i % 100 == 0) updateProgress(taskId, i); // 更新進度
            }
            completeTask(taskId);
        } catch (TaskCancelledException e) {
            markCancelled(taskId);
        }
    }
    public void cancel() { this.cancelled = true; }
}

// Servlet 取消端點
@WebServlet("/task/cancel")
public class CancelTaskServlet extends HttpServlet {
    protected void doPost(HttpServletRequest req, HttpServletResponse resp) {
        String taskId = req.getParameter("taskId");
        TaskManager.getTask(taskId).ifPresent(ExportTask::cancel);
    }
}

// 前端頁面
window.addEventListener('beforeunload', () => {
    navigator.sendBeacon('/task/cancel', 'taskId=' + currentTaskId);
});

替代架構:異步任務隊列

  • 請求提交任務 → 返回 taskId → 前端輪詢進度/結果。
  • 任務由專門的 Worker 線程池/消息隊列(RabbitMQ、Redis Stream、Quartz)執行,與 HTTP 線程完全解耦。
  • 取消操作只需在隊列中標記/移除任務,Worker 下次輪詢感知到即停止。

核心原則:HTTP 請求不應直接承載長時間業務邏輯,引入異步任務模式可實現可控的啟動、監控、取消、重試,是企業級應用的標準架構。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved