协程的等待与唤醒机制

为了存储协程的等待上下文, TrueAsync 使用 Waker 结构。 它充当协程与其订阅的事件之间的纽带。 借助 Waker,协程始终确切地知道它正在等待哪些事件。

Waker 结构

出于内存优化目的,waker 直接集成在协程结构(zend_coroutine_t)中, 这避免了额外的分配并简化了内存管理, 尽管代码中为了向后兼容仍使用 zend_async_waker_t *waker 指针。

Waker 保存等待事件的列表并聚合等待结果或异常。

struct _zend_async_waker_s {
    ZEND_ASYNC_WAKER_STATUS status;

    // 协程正在等待的事件
    HashTable events;

    // 上一次迭代中触发的事件
    HashTable *triggered_events;

    // 唤醒结果
    zval result;

    // 错误(如果唤醒由错误引起)
    zend_object *error;

    // 创建点(用于调试)
    zend_string *filename;
    uint32_t lineno;

    // 析构函数
    zend_async_waker_dtor dtor;
};

Waker 状态

在协程生命周期的每个阶段,Waker 处于五种状态之一:

Waker 状态

typedef enum {
    ZEND_ASYNC_WAKER_NO_STATUS, // Waker 未激活
    ZEND_ASYNC_WAKER_WAITING,   // 协程正在等待事件
    ZEND_ASYNC_WAKER_QUEUED,    // 协程已排队等待执行
    ZEND_ASYNC_WAKER_IGNORED,   // 协程被跳过
    ZEND_ASYNC_WAKER_RESULT     // 结果可用
} ZEND_ASYNC_WAKER_STATUS;

协程以 NO_STATUS 开始 – Waker 存在但未激活;协程正在执行。 当协程调用 SUSPEND() 时,Waker 转换为 WAITING 并开始监控事件。

当其中一个事件触发时,Waker 转换为 QUEUED:结果被保存, 协程被放入 Scheduler 队列中等待上下文切换。

IGNORED 状态用于协程已在队列中但必须被销毁的情况。 此时,Scheduler 不会启动协程,而是直接终结其状态。

当协程醒来时,Waker 转换为 RESULT 状态。 此时,waker->error 被传递到 EG(exception)。 如果没有错误,协程可以使用 waker->result。例如,result 就是 await() 函数返回的内容。

创建 Waker

// 获取 waker(如果不存在则创建)
zend_async_waker_t *waker = zend_async_waker_define(coroutine);

// 为新的等待重新初始化 waker
zend_async_waker_t *waker = zend_async_waker_new(coroutine);

// 带超时和取消
zend_async_waker_t *waker = zend_async_waker_new_with_timeout(
    coroutine, timeout_ms, cancellation_event);

zend_async_waker_new() 析构现有的 waker 并将其重置为初始状态。这允许不需要分配 就能重用 waker。

订阅事件

zend_async_API.c 模块提供了几个现成的函数来将协程绑定到事件:

zend_async_resume_when(
    coroutine,        // 要唤醒哪个协程
    event,            // 要订阅哪个事件
    trans_event,      // 转移事件所有权
    callback,         // 回调函数
    event_callback    // 协程回调(或 NULL)
);

resume_when 是主要的订阅函数。 它创建一个 zend_coroutine_event_callback_t,将其 绑定到事件和协程的 waker。

作为回调函数,您可以使用三个标准函数之一, 取决于您希望如何唤醒协程:

// 成功结果
zend_async_waker_callback_resolve(event, callback, result, exception);

// 取消
zend_async_waker_callback_cancel(event, callback, result, exception);

// 超时
zend_async_waker_callback_timeout(event, callback, result, exception);