核心队列接口设计:QueueTask 抽象、优先级枚举、任务状态生命周期定义
一个轻量级后台队列的可靠性,首先取决于它的任务模型设计。本章定义整个队列框架的三个核心接口构件:任务状态生命周期、优先级枚举以及QueueTask 抽象类。这些接口将成为后续所有调度器、队列存储和容错机制的共同契约。
3.1 任务状态生命周期
每个后台任务从创建到最终结束,会经历以下 5 个状态。状态转换严格单向且不可回退。
| 状态 | 名称 | 转换条件 |
|---|---|---|
| INITIAL | 初始 | 任务刚被创建,尚未入队。 |
| ENQUEUED | 已入队 | 任务被成功写入队列存储。 |
| RUNNING | 运行中 | 调度器从队列取出并开始执行。 |
| COMPLETED | 已完成 | 任务逻辑正常结束。 |
| FAILED | 失败 | 任务抛出未捕获异常或超时。 |
设计要点:不允许从 RUNNING 回退到 ENQUEUED,也不支持 COMPLETED 重新激活。如果需要重试,应当创建一个新的任务实例或使用“重试计数”字段在 FAILED 状态下产生副本。
3.2 优先级枚举
为了支持业务分级,队列需要内置优先级机制。枚举值越大优先级越高。
enum class TaskPriority(val level: Int) {
LOW(0),
MEDIUM(5),
HIGH(10)
}
优先级仅在出队阶段生效:调度器每次从队列头部取任务时,总是优先取出 HIGH 级任务,其次 MEDIUM,最后 LOW。同优先级内部按照入队时间 FIFO 排序。
3.3 QueueTask 抽象类
所有业务任务必须继承的基类,它封装了状态管理和唯一标识。
abstract class QueueTask(
open val id: String = UUID.randomUUID().toString(),
open val priority: TaskPriority = TaskPriority.MEDIUM
) {
@Volatile
var state: TaskState = TaskState.INITIAL
internal set
@Volatile
var createTime: Long = System.currentTimeMillis()
internal set
@Volatile
var retryCount: Int = 0
internal set
/** 具体任务逻辑,由子类实现 */
abstract suspend fun execute()
/** 任务失败后的回调,默认空实现 */
open suspend fun onFailure(throwable: Throwable) {}
/** 任务完成后的回调,默认空实现 */
open suspend fun onCompleted() {}
}
⚠ 线程安全注意:
state、createTime、retryCount 均使用 @Volatile 保证可见性,但复合操作(如状态 + 重试计数同时更新)需要由调度器在外层加锁或使用原子引用。
3.4 接口设计总览
| 关注点 | 接口/类 | 核心字段/方法 |
|---|---|---|
| 任务标识 | QueueTask.id | UUID 字符串,全局唯一 |
| 优先级 | TaskPriority 枚举 | LOW(0)、MEDIUM(5)、HIGH(10) |
| 状态管理 | TaskState 枚举 | 5 个状态,单向流转 |
| 业务执行 | QueueTask.execute() | suspend 函数,由调度器在协程中调用 |
| 生命周期回调 | onFailure / onCompleted | 可选覆盖,用于日志、告警、重试决策 |
3.5 状态流转与优先级队列示意
下图为任务状态机与优先级队列的关系图,展示了任务从创建到完成/失败的全路径,以及优先级如何影响出队顺序。
接口一致性约束:所有继承自
QueueTask 的子类必须在 execute() 内部正确处理挂起点(suspend),不得长期阻塞线程。调度器依赖 isActive 或当前协程的取消状态来中断超时任务。
3.6 常见设计错误
- 状态暴露为 var: 外部可直接修改
task.state,破坏状态机完整性。正确做法是state使用internal set,仅允许同一模块内的调度器写入。 - 优先级与延迟混用: 优先级控制的是出队顺序,不是执行时刻或延迟时间。如果需要延迟执行,应额外增加
scheduledTime字段。 - 忽略序列化兼容: 如果队列需要持久化(如磁盘/数据库),
QueueTask的子类必须支持序列化和反序列化,否则重启后任务丢失。