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