3. 任务模型定义:任务接口、状态枚举、元数据设计

离线任务流管理的核心是建立清晰、可扩展的任务模型。一个良好的任务模型能降低协程调度与状态追踪的复杂度,并为重试、依赖、优先级等高级特性提供基础。本章从三个维度定义任务模型:任务接口(行为契约)、状态枚举(生命周期)、元数据结构(描述信息)。

关键设计原则:
  • 单一职责:任务只描述“做什么”和“状态是什么”,不耦合调度逻辑。
  • 可序列化:元数据必须支持 JSON/YAML 序列化,用于持久化与恢复。
  • 协程友好:接口返回 coroutine 或 future,避免阻塞。

3.1 任务接口(Task Interface)

任务接口定义了协程调度器与具体任务之间的交互协议。一个最小化接口包含以下方法:

方法 返回值 说明
execute() coroutine<void> 或 future<void> 执行任务主体,可被协程调度器挂起/恢复
cancel() bool 请求取消任务,返回是否成功发起取消
status() TaskStatus 获取当前任务状态
metadata() const TaskMeta& 返回任务只读元数据引用

以下是基于 C++20 协程的最小接口示例(核心方法):

class Task {
public:
    virtual ~Task() = default;

    // 核心执行协程 —— 协程体可包含 co_await 挂起点
    virtual Awaitable<void> execute() = 0;

    // 安全取消 — 协程内部应定期检查 cancellation_
    virtual bool cancel() noexcept {
        _.request_cancel();
        return true;
    }

    // 状态查询
    TaskStatus status() const noexcept { return status_; }

    // 元数据只读引用
    const TaskMeta& metadata() const noexcept { return meta_; }

protected:
    TaskMeta meta_;
    TaskStatus status_ = TaskStatus::Pending;
    CancellationToken _;
};
协程接口优势: 相比传统回调或 future 链式调用,execute() 返回协程对象使得调度器可以自然地挂起任务、等待 IO 或依赖,而不占用线程。取消机制通过 cancellation_ 实现协作式取消,避免强制中断导致资源泄漏。

3.2 任务状态枚举(TaskStatus)

任务状态必须覆盖从创建到结束的完整生命周期,且支持状态迁移的合法性校验。以下枚举定义了六个标准状态:

枚举值 含义 可迁移至
Pending 已创建但未调度执行 Running, Cancelled
Running 正在执行(协程恢复中) Completed, Failed, Cancelling
Cancelling 收到取消请求,正在执行清理 Cancelled, Failed
Completed 正常执行完毕 (终态)
Failed 执行过程中抛出异常或返回错误 (终态,除非重试)
Cancelled 被成功取消 (终态)

状态枚举的 C++ 定义:

enum class TaskStatus : uint8_t {
    Pending    = 0,
    Running    = 1,
    Cancelling = 2,
    Completed  = 3,
    Failed     = 4,
    Cancelled  = 5
};

// 判断是否终态
inline bool isTerminal(TaskStatus s) noexcept {
    return s == TaskStatus::Completed ||
           s == TaskStatus::Failed    ||
           s == TaskStatus::Cancelled;
}
注意: 务必在状态迁移时做合法性检查。例如,不允许直接从 Pending 跳转到 Completed。非法状态迁移通常是协程调度 bug 的源头。建议在调试阶段使用 assert 或日志记录每次状态变更。

3.3 任务元数据设计(TaskMeta)

元数据描述任务的静态信息与运行时上下文,用于调度决策、持久化恢复、监控展示。推荐结构如下:

字段 类型 说明
task_id std::string / UUID 全局唯一标识
name std::string 可读名称,用于日志/监控
create_time std::chrono::system_clock::time_point 任务创建时间
priority uint8_t (0~255) 数值越大优先级越高
max_retries uint32_t 最大重试次数(0 表示不重试)
retry_delay_ms uint32_t 重试间隔(毫秒)
dependencies std::vector<std::string> 依赖的任务 ID 列表
tag std::string 自定义标签,用于过滤/分组
extra nlohmann::json / std::any 扩展信息(用户自定义参数)

JSON 序列化示例:

{
  "task_id": "a1b2c3d4-5678-90ab-cdef-1234567890ab",
  "name": "数据清洗",
  "create_time": "2025-03-15T10:30:00Z",
  "priority": 100,
  "max_retries": 3,
  "retry_delay_ms": 5000,
  "dependencies": ["prepare_data", "validate_schema"],
  "tag": "batch_etl",
  "extra": {
    "source": "s3://bucket/input.csv",
    "target": "s3://bucket/output.parquet"
  }
}

3.4 任务模型整体结构

下图展示任务接口、状态枚举、元数据之间的逻辑关系,以及它们在协程调度器中的位置:

任务模型核心结构 Task 接口 - execute() → coroutine - cancel() → bool - status() → TaskStatus - metadata() → TaskMeta 状态枚举 Pending → Running Running → Completed/Failed Running → Cancelling Cancelling → Cancelled 元数据 TaskMeta task_id (UUID) priority, max_retries dependencies extra (json) 协程调度器使用方式 1. 调度器遍历等待队列,检查任务依赖和优先级。 2. 对每个就绪任务调用 execute() 启动协程,并设置状态为 Running。 3. 协程可被挂起(等待 IO/依赖),调度器在恢复时更新状态。 状态迁移安全校验 - 只有 Pending → Running → (Completed / Failed / Cancelling) → Cancelled 是合法路径。 - 终态任务不可再被调度器选中,元数据保留用于审计。 - 重试时复制元数据并重置状态为 Pending,新 task_id 产生。

3.5 实践建议

  • 接口最小化:除 execute/status/metadata 外,避免在接口中引入过多方法,保持 SPI 纯洁。
  • 状态机严格:在每次状态变更时记录时间戳(可加入 meta 的 status_history 字段),便于排障。
  • 元数据可扩展:使用 extra 字段承接业务参数,避免修改基类结构。
  • 协程取消:在 execute() 协程体内定期 co_await _.check_cancel(),实现协作式取消。
调试技巧: 在开发阶段,为每个任务开启 TaskMeta::trace_id(关联一次调度生命周期),结合结构日志可以快速定位协程挂起/恢复的时序问题。