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

DeferredResult未正确设置结果:异步长轮询接口异常求助

DeferredResult始终返回"in-progress"的问题分析与修复

问题根源

每次前端发起轮询请求时,接口都会创建全新的DeferredResult实例,而异步任务仅绑定在第一次请求生成的DeferredResult上。后续轮询拿到的新实例完全独立于异步任务,自然永远接收不到第三方API返回的最终结果。

核心修复思路

需要将任务的状态、结果(或关联的DeferredResult)通过唯一任务ID绑定,存储在跨请求的容器中(如内存缓存、分布式缓存),让每一次轮询都能通过任务ID关联到同一个任务的状态。

具体修复步骤

  1. 生成唯一任务ID:第一次请求时,生成全局唯一的任务ID,作为后续轮询的关联标识。
  2. 存储任务上下文:将任务ID、初始状态(in-progress)存入缓存容器。
  3. 异步任务更新状态:第三方API调用完成后,更新缓存中对应任务的状态和结果。
  4. 轮询关联任务:后续轮询请求携带任务ID,接口从缓存中查询对应任务状态:
    • 未完成则返回"in-progress";
    • 已完成则返回最终结果并清理缓存;
    • 任务不存在则返回错误提示。

示例代码(Spring Boot)

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;

@RestController
public class AsyncTaskController {
    // 内存缓存存储任务信息,多实例部署需替换为Redis等分布式缓存
    private final Map<String, TaskContext> taskCache = new ConcurrentHashMap<>();

    // 任务上下文实体
    static class TaskContext {
        String status;
        Object result;
    }

    @GetMapping("/init-task")
    public Map<String, Object> initTask() {
        String taskId = UUID.randomUUID().toString();
        TaskContext context = new TaskContext();
        context.status = "in-progress";
        taskCache.put(taskId, context);

        // 启动异步任务调用第三方API
        new Thread(() -> {
            try {
                // 模拟第三方API耗时1分钟
                Thread.sleep(60000);
                // 模拟第三方返回结果
                Object thirdPartyData = "第三方接口返回的业务数据";
                // 更新任务状态与结果
                context.status = "completed";
                context.result = thirdPartyData;
            } catch (InterruptedException e) {
                context.status = "failed";
                context.result = "任务执行中断:" + e.getMessage();
            } finally {
                // 5分钟后自动清理缓存,避免内存泄漏
                new Thread(() -> {
                    try {
                        Thread.sleep(5 * 60000);
                        taskCache.remove(taskId);
                    } catch (InterruptedException ignored) {}
                }).start();
            }
        }).start();

        // 立即返回任务ID与初始状态
        return Map.of("taskId", taskId, "status", "in-progress");
    }

    @GetMapping("/poll-task-status")
    public Map<String, Object> pollTask(@RequestParam String taskId) {
        TaskContext context = taskCache.get(taskId);
        if (context == null) {
            return Map.of("status", "error", "message", "任务不存在或已过期");
        }

        return switch (context.status) {
            case "in-progress" -> Map.of("taskId", taskId, "status", "in-progress");
            case "completed" -> Map.of("taskId", taskId, "status", "completed", "result", context.result);
            case "failed" -> Map.of("taskId", taskId, "status", "failed", "message", context.result);
            default -> Map.of("status", "error", "message", "未知任务状态");
        };
    }
}

注意事项

  • 多实例部署适配:如果服务是多实例集群,必须使用Redis等分布式缓存替代内存HashMap,否则跨实例的轮询请求无法获取任务状态。
  • 缓存清理策略:务必设置缓存过期时间或定时清理逻辑,防止无效任务占用内存。
  • 前端轮询逻辑:前端需保存第一次请求返回的taskId,后续每10秒携带该ID发起轮询,直到拿到完成/失败状态。

内容的提问来源于stack exchange,提问作者ajay entertainment

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.17 11:23:18