版本:0.2 | 日期:2026-06-26 | 路径: components/xt_task/xt_task.h
一、概述
xt_task 是 xt-sdk 的协程式任务调度模块,基于 timer2 和 event 实现的轻量级协程。通过 LC(Local Continuation)行号跳转方案,在单线程内实现任务的挂起/恢复,无需 OS 线程支持。
1.1 设计原则
- 静态数组管理 :所有 task 槽位在 xt_task_array[XT_TASK_NUM] 中预分配,无动态内存
- 单线程协作 :所有 task 在同一线程执行,无临界区保护
- 父子任务两级 :支持父任务调用子任务并等待完成,不支持孙子任务
- 事件/定时器双通道唤醒 :task 可同时等待事件和超时,先到先得
- 幂等初始化 : xt_task_setup() 多次调用安全,仅首次生效
1.2 依赖
二、核心概念
2.1 LC(Local Continuation)机制
LC 通过行号跳转实现协程的挂起/恢复。每个 task 有一个 lc 字段( volatile int ):
- lc == 0 — 初始状态或已完成
- lc != 0 — 挂起点行号,resume 时跳转到对应 case 标签
LC 机制基于 switch-case + _Pragma("GCC diagnostic ignored \"-Wimplicit-fallthrough\\"\") 实现,无需汇编或 setjmp/longjmp。
2.2 任务对象
task 对象内嵌定时器和事件等待字段,支持双通道唤醒:
2.3 挂起与恢复
协程通过宏挂起(yield),通过回调恢复:
- 挂起:设置 lc 行号 → return 退出函数
- 恢复:timer2 超时回调或 event handler 重新调用 task entry → lc resume 跳转到挂起点
yield 后局部变量丢失 :除 XT_TASK_YIELD_FLAG 外,所有局部变量在 yield 后不保留。跨 yield 的状态应通过 user_data 或全局/静态变量传递。
三、API 参考
以下为函数签名大纲,完整的参数说明、返回值、注意事项请查看 xt_task(协程式任务调度) 。
3.1 系统管理
3.2 任务生命周期
3.3 协程框架宏
3.4 挂起宏
xt_task_wait_until 唤醒后通过 xt_event_get_current_event_id() 判断唤醒原因:非 INVALID=事件唤醒,INVALID=超时唤醒。
使用示例:
static void wait_task(void *user_data) {
printf("data received\n");
} else {
printf("timeout!\n");
}
}
#define XT_EVENT_INVALID_ID
无效事件 ID,兼作空闲槽位标记
xt_event_id_t xt_event_get_current_event_id(void)
获取当前正在处理的事件 ID
#define XT_TASK_END()
协程任务函数结尾
#define XT_TASK_BEGIN()
协程任务函数开头
#define xt_task_wait_until(event_id, timeout)
等待指定事件或超时
3.5 子任务宏
使用示例:
static void subtask(void *user_data) {
printf("subtask start\n");
printf("subtask done\n");
}
static void parent_task(void *user_data) {
printf("before subtask\n");
printf("after subtask\n");
}
#define xt_task_call_subtask(entry, user_data)
调用子任务并等待其完成
#define xt_task_delay(ms)
延时等待若干毫秒后继续执行
3.6 内部 API(用户不应直接调用)
四、事件+超时协作机制
xt_task_wait_until 同时启动定时器和订阅事件,两个通道均可唤醒 task,但只会触发一次——先到先得。
4.1 事件先到达
timeline:
t=0 task 调用 xt_task_wait_until(EVT_A, 200)
→ 订阅 EVT_A + 启动定时器 200ms
→ lc yield,task 挂起
t=50 xt_event_publish(EVT_A) + xt_event_handler()
→ xt_task_event_handler(EVT_A)
→ xt_timer2_stop(&task->timer) // 停止定时器
→ task->wait_event = INVALID // 清除等待
→ _xt_task_call(task) // 恢复 task
→ lc resume 跳转到 case 标签后
→ xt_event_get_current_event_id() != INVALID → 事件唤醒
4.2 超时先到达
timeline:
t=0 task 调用 xt_task_wait_until(EVT_A, 200)
→ 订阅 EVT_A + 启动定时器 200ms
t=200 timer2 超时回调 _xt_task_timer_cb()
→ task->wait_event = INVALID // 清除等待
→ _xt_task_call(task) // 恢复 task
→ lc resume 跳转到 case 标签后
→ xt_event_get_current_event_id() == INVALID → 超时唤醒
t=250 xt_event_publish(EVT_A) 到达 // 此时 task 已不在等待
→ xt_task_event_handler 遍历,wait_event 不匹配,忽略
4.3 互斥保证
关键点: 事件和超时服务函数都会先清除互斥状态再唤醒 task 。
| 唤醒路径 | 操作 |
| 事件到达 | xt_timer2_stop 停止定时器 → 清除 wait_event → resume |
| 超时到达 | 回调中清除 wait_event (定时器节点已自动摘下)→ resume |
因此两个唤醒源不可能同时触发同一个 task 的 resume。
4.4 纯事件等待(xt_task_wait_event)
xt_task_wait_event 传入 XT_TASK_WAIT_FOREVER ,不启动定时器。task 只能由事件唤醒。
五、父子任务机制
5.1 工作流程
1. 父任务调用 xt_task_call_subtask(subtask, data)
├─ xt_task_start(subtask, data) 分配槽位
│ ├─ s_xt_task_current = parent_task
│ ├─ parent_id != INLIVAD? 检查孙任务限制
│ └─ 设置 child.parent_id = parent.task_id
│
├─ _xt_task_call(&child)
│ ├─ ptr_parent_task = s_xt_task_current (保存父任务)
│ ├─ s_xt_task_current = &child
│ ├─ child.entry(child.user_data) // 执行子任务
│ │ 子任务执行到 yield → return 挂起
│ └─ s_xt_task_current = ptr_parent_task (恢复)
│
└─ 父任务 call_subtask 宏中 XT_TASK_YIELD_FLAG == 0 → return 挂起
2. 子任务完成后(_xt_task_call 中)
├─ if (child.lc == 0)
│ ├─ uint8_t parent_id = child.parent_id
│ ├─ xt_timer2_stop(&child.timer)
│ ├─ memset(child, 0, sizeof(xxxx)) // 回收子任务槽位
│ ├─ s_xt_task_current = &xt_task_array[parent_id] // 切到父任务
│ └─ parent.entry(parent.user_data) // 恢复父任务执行
│ ├─ 若 parent.lc == 0 → 父任务也完成 → 回收
│ └─ 若 parent.lc != 0 → 父任务在 yield 中 → resume 继续
5.2 关键规则
- 子任务必须 yield :若子任务立即完成(无 delay/wait),在 _xt_task_call 内部会递归调用父任务。这是设计上允许但不推荐的行为,可能导致调用栈增长。
- 不支持孙任务 : _xt_task_alloc 中检查 parent_id != XT_TASK_INLIVAD_ID ,触发 XT_GRANDCHILD_NOT_SUPPORT assert。
- 两级 cleanup :子任务完成 → 回收子任务槽位 → 恢复父任务。若父任务也完成(lc==0),同样回收父任务槽位。
六、常见模式
6.1 主循环模板
void xt_main(void)
{
while (1) {
if (remain == UINT32_MAX) {
osDelay(1000);
} else if (remain > 0) {
osDelay(remain);
}
}
}
xt_err_t xt_event_subscribe(xt_event_id_t event_id, xt_event_cb_t event_cb, void *user_data)
订阅事件(去重匹配)
xt_err_t xt_event_handler(void)
处理事件队列中的所有事件
xt_task_t * xt_task_start(xt_task_entry_t entry, void *user_data)
启动一个协程任务
xt_err_t xt_task_setup(void)
初始化 task 子系统
xt_tick2_t xt_timer2_handler(void)
定时器主循环处理函数,需在主循环中周期性调用
uint32_t xt_tick2_t
tick 类型,32 位无符号
事件发布订阅模块接口(去重匹配、dispatch 上下文栈、临界区保护)
协程式任务调度模块接口(基于 timer2 和 event 的轻量级协程)
新版软件定时器接口(对象化、单链表、new/delete 生命周期)
6.2 完整任务示例
static void led_blink_task(void *user_data)
{
while (1) {
led_on();
led_off();
}
}
static void button_task(void *user_data)
{
void *param;
while (1) {
handle_button_press((int)(uintptr_t)param);
}
}
static void wait_ack_task(void *user_data)
{
send_command();
handle_ack();
} else {
handle_timeout();
}
}
static void seq_task(void *user_data)
{
void *param;
step_start();
step_one();
step_two((int)(uintptr_t)param);
step_finish();
}
#define xt_task_wait_event(event_id, event_param)
等待指定事件(永不超时)
6.3 子任务示例
static void send_data_subtask(void *user_data)
{
uint8_t *data = (uint8_t *)user_data;
uart_send(data, sizeof(data));
printf("send ok\n");
}
}
static void master_task(void *user_data)
{
uint8_t buf[16];
printf("first send complete\n");
printf("second send complete\n");
}
七、限制与注意事项
| 限制 | 说明 |
| 不支持孙任务 | 子任务中不能再调用 xt_task_call_subtask (触发 XT_GRANDCHILD_NOT_SUPPORT assert) |
| 子任务必须 yield | 子任务必须至少一次 delay/wait_event/wait_until(推荐 xt_task_delay(1) ),否则立即完成时递归调用父任务 |
| 单线程协作 | 所有 task 在同一线程执行,无临界区保护。长时间计算会阻塞所有其他 task |
| task 数组满 | 默认 XT_TASK_NUM = 10 ,超限后 xt_task_start assert 并返回 NULL |
| 栈大小 | 每次 yield → resume 都通过函数返回/重入实现,无独立栈。task 内部栈变量在 yield 后丢失(除 XT_TASK_YIELD_FLAG 外) |
| 递归深度 | 无 yield 的立即完成子任务会递归调用父任务,两层可接受,三层不支持 |
| lc 变量 | volatile int lc 存储在 task 结构体中,跨 yield 保持。用户不应修改 |
| 任务函数内局部变量 | yield 后不保留,需跨 yield 的状态应定义在外部(全局/静态变量或通过 user_data 传递) |
| -Wimplicit-fallthrough | lc 机制的 switch-case 会触发此警告,可通过 #pragma 抑制 |
| timeout 上限 | 不能超过 XT_TIMER2_TIMEOUT_MAX (INT32_MAX),超限触发 assert |
| 事件参数访问 | 仅在 xt_task_wait_event resume 后或 xt_task_wait_until 事件唤醒后可安全访问,超时唤醒时无有效事件上下文 |
八、测试
测试套件位于 tests/xt_task/ 和 tests/xt_event_and_timer/ :
cd tests/xt_task
xt --target windows/simulator fullclean build
xt --target windows/simulator run