Skills

Async Task Pattern

当 API/任务可能执行超过 10 秒(批量数据处理、远程 API 批量调用、全表扫描、跨租户聚合)时触发。防止同步接口被网关 30s...

Install

npx skills add doccker/cc-use-exp --skill async-task-pattern -g -a claude-code -y
Repository
doccker/cc-use-exp1.1k
Category
Engineering
Updated
2mo ago
License
See repository
Context cost
Mediumabout 1,666 tokens
Runs scripts
No, instructions only

What it does

当 API/任务可能执行超过 10 秒(批量数据处理、远程 API 批量调用、全表扫描、跨租户聚合)时触发。防止同步接口被网关 30s 超时切断、用户重复点击触发并发、状态缓存内存泄漏等问题。提供异步任务状态机标准模板。

异步任务模式规范

当一个操作可能跑超过 10 秒,必须改成 triggerAsync() + getStatus() 的异步状态机模式。 不要让用户盯着一个会超时的 loading 转圈,更不要让他点第二下。

与 query-performance-safety 配合:先用后者把同步性能压到能跑完的范围;只有"再优化也压不到 10s 以内"时,才走本规范。


触发条件

只要满足任一条件,就属于本 skill 的覆盖范围:

  • 单次操作处理数据量 ≥ 1000 条(批量导入、全表扫描、跨租户聚合)
  • 链路里有远程 API 批量调用,且 batch size × 单次延迟 > 5s
  • 历史上同步实现已经出过 504/502/30s timeout
  • 用户描述里出现"卡住"、"转圈很久"、"点完没反应"

陷阱 #1: 同步阻塞被网关切断

场景: 6000 条商品查重,同步接口 30s 超时;前端无回执,用户重复点击。

错误示例

// ❌ HTTP 请求线程内串行跑 N 次远程调用 + JPA 写库
@PostMapping("/duplicates/check")
public ApiResponse<Map<String, List<ProductDTO>>> checkDuplicates() {
    return ApiResponse.success(productService.findDuplicateProducts());
    // 6000 条 × 4 次隐藏查询 = 24000 次 SQL,必然 > 30s
}

后果:

  • Nginx/网关默认 30-60s 截断 → 用户看到 502/504
  • 用户重试 → 后端线程池被占满 → 雪崩
  • 前端 loading 状态没法跨刷新保持

正确模板(Spring 版)

@Service
public class DuplicateChecker {
    private static final long FINISHED_STATE_TTL_MINUTES = 60;
    private final Executor executor;
    private final ProductService productService;

    // 关键:状态缓存按租户隔离,永远不要让它无界增长
    private final Map<Long, CheckState> tenantStates = new ConcurrentHashMap<>();

    public CheckState triggerAsync() {
        evictExpiredStates();
        Long tenantId = TenantContext.getTenantId();
        // 同租户已 RUNNING:返回当前状态,不重复启动
        CheckState existing = tenantStates.get(tenantId);
        if (existing != null && existing.getStatus() == Status.RUNNING) {
            return existing.snapshot();
        }
        CheckState state = new CheckState();
        state.setStatus(Status.RUNNING);
        state.setStartedAt(LocalDateTime.now());
        tenantStates.put(tenantId, state);
        executor.execute(() -> {
            try {
                TenantContext.setTenantId(tenantId);  // 子线程必须恢复上下文
                run(tenantId, state);
            } finally {
                TenantContext.clear();
            }
        });
        return state.snapshot();
    }
Read the whole SKILL.md on GitHub

More in Engineering