【C++】C++20协程的await_transform和coroutine_handle

文章目录

深入理解 C++20 协程核心机制

一、核心概念拆解(由浅入深)

C++20 协程的底层核心组件包括 noop_coroutine 系列工具、协程句柄 std::coroutine_handle,以及控制协程挂起恢复的关键逻辑 await_transformawait_suspend

1. std::coroutine_handle - 协程的"遥控器"

std::coroutine_handle<Promise> 是协程的句柄,相当于协程的"遥控器"。通过它可以:

  • 恢复 (resume) 协程执行
  • 销毁 (destroy) 协程状态
  • 检查 (done) 协程是否完成
  • 访问 (promise) 协程的承诺对象

两种形式:

cpp 复制代码
// 1. 通用协程句柄(类型擦除)
std::coroutine_handle<> h;  // 等价于 std::coroutine_handle<void>

// 2. 具体承诺类型句柄
struct MyPromise { /* ... */ };
std::coroutine_handle<MyPromise> specific_h;

重要澄清:

  • std::coroutine_handle<>std::coroutine_handle<void> 的别名
  • std::coroutine_handle<void>std::coroutine_handle<noop_coroutine_promise>不同的类型
  • std::coroutine_handle<> 不能直接操作 noop_coroutine_promise 类型的协程

2. noop_coroutine 系列 - 空操作协程

"noop" = "no operation"(无操作)。这是一组特殊工具,用于创建无实际效果的协程句柄。

组件说明:

cpp 复制代码
// 1. 空操作协程的承诺类型
struct noop_coroutine_promise;

// 2. 空操作协程句柄的类型别名
using noop_coroutine_handle = 
    std::coroutine_handle<noop_coroutine_promise>;

// 3. 工厂函数
noop_coroutine_handle noop_coroutine() noexcept;

关键特性:

  • resume()destroy() 是空操作(无效果)
  • done() 总是返回 true
  • 所有 noop_coroutine() 返回的句柄都相等
  • 主要用于安全的占位符或默认值

为什么需要?

cpp 复制代码
// 场景:异步操作类,需要一个安全的协程句柄作为成员默认值
class AsyncOperation {
    std::coroutine_handle<> continuation_ = std::noop_coroutine();
    // ^ 使用 noop 协程作为安全的默认值
public:
    void complete() {
        if (continuation_ != std::noop_coroutine()) {
            continuation_.resume();  // 只恢复真实的协程
        }
    }
};

3. await_transform() - 等待转换器

await_transform 是 Promise 类型的可选成员函数,用于拦截和转换 co_await 表达式。

工作流程:

cpp 复制代码
co_await expression;
// 编译器会转换为:
co_await promise.await_transform(expression);  // 如果有定义
// 否则:
co_await expression;  // 直接使用原表达式

4. await_suspend() - 等待挂起逻辑

await_suspend 是 Awaitable 类型的核心成员,控制协程挂起后的行为。

三种返回值类型:

cpp 复制代码
// 1. 返回 void - 挂起后不自动恢复
void await_suspend(std::coroutine_handle<> h) {
    // 当前协程已挂起,句柄 h 可供稍后恢复
    schedule_for_later(h);  // 可以存储 h 供以后使用
}

// 2. 返回 bool - 控制是否挂起
bool await_suspend(std::coroutine_handle<> h) {
    if (should_suspend) {
        return true;   // true = 挂起协程
    } else {
        return false;  // false = 不挂起,立即恢复
    }
}

// 3. 返回 coroutine_handle<> - 挂起并恢复其他协程
std::coroutine_handle<> await_suspend(std::coroutine_handle<> h) {
    // 返回要恢复的其他协程句柄
    return other_coroutine_handle;  
    // ^ 当前协程挂起,自动恢复 other_coroutine_handle
}

二、完整代码示例(修正版)

原代码存在关键问题:await_suspend 返回 noop_coroutine() 会导致当前协程永远挂起。以下是修正后的完整示例:

cpp 复制代码
#include <coroutine>
#include <iostream>
#include <string>
#include <functional>

// ========== 示例1:基本用法 ==========
struct BasicPromise {
    struct promise_type {
        BasicPromise get_return_object() { return {}; }
        std::suspend_never initial_suspend() { return {}; }
        std::suspend_never final_suspend() noexcept { return {}; }
        void unhandled_exception() { std::terminate(); }
        void return_void() {}
    };
};

// 示例协程
BasicPromise basic_coroutine() {
    std::cout << "Basic coroutine executing\n";
    co_return;
}

// ========== 示例2:使用 await_transform ==========
struct LoggerPromise {
    struct promise_type {
        LoggerPromise get_return_object() { return {}; }
        std::suspend_never initial_suspend() { return {}; }
        std::suspend_never final_suspend() noexcept { return {}; }
        void unhandled_exception() { std::terminate(); }
        void return_void() {}
        
        // 拦截所有 co_await,添加日志
        template<typename T>
        auto await_transform(T&& value) {
            struct LoggingAwaitable {
                T value;
                
                bool await_ready() const noexcept { 
                    std::cout << "[LOG] Checking if ready\n";
                    return false; 
                }
                
                void await_suspend(std::coroutine_handle<> h) {
                    std::cout << "[LOG] Suspending coroutine\n";
                    // 立即恢复,演示目的
                    std::cout << "[LOG] Immediately resuming\n";
                    h.resume();
                }
                
                T await_resume() const {
                    std::cout << "[LOG] Resuming coroutine\n";
                    return value;
                }
            };
            return LoggingAwaitable{std::forward<T>(value)};
        }
    };
};

LoggerPromise logging_coroutine() {
    std::cout << "Before co_await\n";
    int result = co_await 42;
    std::cout << "After co_await, got: " << result << "\n";
    co_return;
}

// ========== 示例3:await_suspend 不同返回值 ==========
struct SuspensionDemo {
    struct promise_type {
        SuspensionDemo get_return_object() { return {}; }
        std::suspend_never initial_suspend() { return {}; }
        std::suspend_never final_suspend() noexcept { return {}; }
        void unhandled_exception() { std::terminate(); }
        void return_void() {}
    };
    
    // 演示不同返回值的 Awaitable
    struct BoolAwaitable {
        bool should_suspend;
        
        bool await_ready() const noexcept { 
            std::cout << "BoolAwaitable::await_ready\n";
            return false; 
        }
        
        // 返回 bool 版本
        bool await_suspend(std::coroutine_handle<> h) {
            std::cout << "BoolAwaitable::await_suspend\n";
            if (should_suspend) {
                // 存储句柄供以后恢复
                std::cout << "Suspending, will resume later\n";
                // 实际应用中,这里可能会将 h 加入任务队列
                stored_handle = h;
                return true;  // 挂起
            } else {
                std::cout << "Not suspending\n";
                return false; // 不挂起
            }
        }
        
        void await_resume() {
            std::cout << "BoolAwaitable::await_resume\n";
        }
        
        static inline std::coroutine_handle<> stored_handle;
    };
    
    struct HandleAwaitable {
        std::coroutine_handle<> next_handle;
        
        bool await_ready() const noexcept { 
            std::cout << "HandleAwaitable::await_ready\n";
            return false; 
        }
        
        // 返回 coroutine_handle 版本
        std::coroutine_handle<> await_suspend(std::coroutine_handle<> h) {
            std::cout << "HandleAwaitable::await_suspend\n";
            std::cout << "Suspending current coroutine\n";
            if (next_handle) {
                std::cout << "Will resume another coroutine\n";
                return next_handle;  // 挂起当前,恢复下一个
            } else {
                std::cout << "No coroutine to resume\n";
                return std::noop_coroutine();  // 只挂起,不恢复任何协程
            }
        }
        
        void await_resume() {
            std::cout << "HandleAwaitable::await_resume\n";
        }
    };
};

// ========== 示例4:noop_coroutine 的正确使用 ==========
void demonstrate_noop_coroutine() {
    std::cout << "\n=== Demonstrating noop_coroutine ===\n";
    
    // 获取 noop 协程句柄
    auto noop_h = std::noop_coroutine();
    std::cout << "noop_coroutine_handle obtained\n";
    
    // 特性演示
    std::cout << "noop_h.done() = " << noop_h.done() << "\n";  // true
    
    // 这些调用都无实际效果
    noop_h.resume();    // 无效果
    noop_h.destroy();   // 无效果
    
    // 比较两个 noop 句柄
    auto noop_h2 = std::noop_coroutine();
    std::cout << "noop_h == noop_h2: " << (noop_h == noop_h2) << "\n";  // true
    
    // 不能转换为普通协程句柄
    // std::coroutine_handle<void> void_h = noop_h;  // 错误!类型不同
    std::coroutine_handle<> void_h = std::coroutine_handle<>::from_address(noop_h.address());
    std::cout << "Converted to void handle\n";
}

// ========== 示例5:综合应用 ==========
class TaskScheduler {
private:
    std::vector<std::coroutine_handle<>> pending_tasks;
    
public:
    // 调度协程执行
    void schedule(std::coroutine_handle<> task) {
        pending_tasks.push_back(task);
    }
    
    // 运行所有任务
    void run_all() {
        while (!pending_tasks.empty()) {
            auto task = pending_tasks.back();
            pending_tasks.pop_back();
            
            if (task && task != std::noop_coroutine()) {
                std::cout << "Running task...\n";
                task.resume();
            }
        }
    }
};

struct SchedulerAwarePromise {
    static inline TaskScheduler* scheduler = nullptr;
    
    struct promise_type {
        SchedulerAwarePromise get_return_object() { return {}; }
        std::suspend_never initial_suspend() { return {}; }
        std::suspend_never final_suspend() noexcept { return {}; }
        void unhandled_exception() { std::terminate(); }
        void return_void() {}
        
        auto await_transform(std::string msg) {
            struct SchedulerAwaitable {
                std::string msg;
                TaskScheduler* scheduler;
                
                bool await_ready() const noexcept { return false; }
                
                // 使用 void 返回值,手动调度
                void await_suspend(std::coroutine_handle<> h) {
                    std::cout << "Scheduling: " << msg << "\n";
                    if (scheduler) {
                        scheduler->schedule(h);  // 将协程加入调度器
                    } else {
                        h.resume();  // 没有调度器,立即恢复
                    }
                }
                
                std::string await_resume() const {
                    return "Completed: " + msg;
                }
            };
            return SchedulerAwaitable{std::move(msg), SchedulerAwarePromise::scheduler};
        }
    };
};

SchedulerAwarePromise scheduled_coroutine(std::string name) {
    std::cout << "Coroutine " << name << " started\n";
    auto result = co_await name;
    std::cout << "Coroutine " << name << " got: " << result << "\n";
    co_return;
}

// ========== 主函数 ==========
int main() {
    std::cout << "=== Example 1: Basic Coroutine ===\n";
    basic_coroutine();
    
    std::cout << "\n=== Example 2: Logging with await_transform ===\n";
    logging_coroutine();
    
    std::cout << "\n=== Example 3: Different await_suspend returns ===\n";
    
    // 演示 noop_coroutine
    demonstrate_noop_coroutine();
    
    std::cout << "\n=== Example 5: Task Scheduler ===\n";
    TaskScheduler scheduler;
    SchedulerAwarePromise::scheduler = &scheduler;
    
    // 创建并调度协程
    scheduled_coroutine("Task1");
    scheduled_coroutine("Task2");
    
    // 运行所有调度的任务
    std::cout << "\nRunning scheduled tasks:\n";
    scheduler.run_all();
    
    std::cout << "\n=== All examples completed ===\n";
    return 0;
}

三、关键场景深入解析

场景1:await_suspend 返回 noop_coroutine() 的正确理解

cpp 复制代码
std::coroutine_handle<> await_suspend(std::coroutine_handle<> h) {
    // 正确用法:当你想挂起当前协程,但不需要立即恢复任何其他协程时
    return std::noop_coroutine();
    // 这意味着:
    // 1. 当前协程被挂起
    // 2. 没有自动恢复其他协程
    // 3. 当前协程的句柄 h 仍然有效,可以在其他地方恢复
}

警告: 如果返回 noop_coroutine() 后没有人恢复原来的协程句柄 h,协程会永远挂起!

场景2:实现协程链

cpp 复制代码
struct CoroutineChain {
    std::coroutine_handle<> next;
    
    bool await_ready() const { return false; }
    
    std::coroutine_handle<> await_suspend(std::coroutine_handle<> h) {
        if (next) {
            return next;  // 挂起当前,恢复下一个
        }
        return std::noop_coroutine();  // 没有下一个,只挂起
    }
    
    void await_resume() {}
};

// 使用
co_await CoroutineChain{next_coroutine_handle};

场景3:条件挂起

cpp 复制代码
struct ConditionalSuspend {
    bool condition;
    
    bool await_ready() const { 
        return condition;  // 如果条件为真,就不挂起
    }
    
    bool await_suspend(std::coroutine_handle<> h) {
        // 只有 await_ready 返回 false 时才调用
        return true;  // 挂起
    }
    
    void await_resume() {}
};

// 使用
co_await ConditionalSuspend{should_suspend};

四、常见错误与修正

错误1:混淆协程句柄类型

cpp 复制代码
// 错误!
std::coroutine_handle<> h = std::noop_coroutine();
// 类型不匹配:左边是 coroutine_handle<void>,右边是 coroutine_handle<noop_coroutine_promise>

// 修正:
auto h = std::noop_coroutine();  // 正确,使用 auto
// 或:
std::coroutine_handle<> void_h = 
    std::coroutine_handle<>::from_address(std::noop_coroutine().address());

错误2:永远挂起的协程

cpp 复制代码
// 错误:返回 noop_coroutine() 但没有人恢复原协程
std::coroutine_handle<> await_suspend(std::coroutine_handle<> h) {
    return std::noop_coroutine();  // 协程永远挂起!
}

// 修正方案1:存储句柄供以后恢复
void await_suspend(std::coroutine_handle<> h) {
    global_task_queue.push(h);  // 加入任务队列
}

// 修正方案2:立即恢复
bool await_suspend(std::coroutine_handle<> h) {
    return false;  // 不挂起,立即恢复
}

错误3:在 await_transform 中忘记转发

cpp 复制代码
// 错误:丢失了原表达式的类型信息
template<typename T>
auto await_transform(T&& value) {
    MyAwaitable awaiter;
    return awaiter;  // 丢失了 value!
}

// 修正:正确转发
template<typename T>
auto await_transform(T&& value) {
    return MyAwaitable{std::forward<T>(value)};
}

五、总结

  1. std::coroutine_handle 是协程操作的核心,有通用 (<>) 和具体 (<Promise>) 两种形式。

  2. noop_coroutine 系列 提供安全的空操作协程句柄,用于占位符或默认值。

  3. await_transform 允许 Promise 拦截和转换 co_await 表达式,实现统一处理逻辑。

  4. await_suspend 的三种返回值:

    • void:挂起,需要手动恢复
    • bool:控制是否挂起
    • coroutine_handle<>:挂起并恢复指定协程
  5. 关键实践

    • 使用 noop_coroutine() 作为安全的协程句柄默认值
    • 确保挂起的协程最终会被恢复(避免内存泄漏)
    • 理解不同类型协程句柄之间的转换规则

掌握这些核心概念,你就能灵活控制 C++20 协程的挂起、恢复和转换逻辑,为构建异步框架、协程调度器等高级应用打下坚实基础。

相关推荐
寻寻觅觅☆7 小时前
东华OJ-基础题-106-大整数相加(C++)
开发语言·c++·算法
fpcc7 小时前
并行编程实战——CUDA编程的Parallel Task类型
c++·cuda
l1t7 小时前
在wsl的python 3.14.3容器中使用databend包
开发语言·数据库·python·databend
赶路人儿8 小时前
Jsoniter(java版本)使用介绍
java·开发语言
ceclar1238 小时前
C++使用format
开发语言·c++·算法
码说AI9 小时前
python快速绘制走势图对比曲线
开发语言·python
Gofarlic_OMS9 小时前
科学计算领域MATLAB许可证管理工具对比推荐
运维·开发语言·算法·matlab·自动化
lanhuazui109 小时前
C++ 中什么时候用::(作用域解析运算符)
c++
charlee449 小时前
从零实现一个生产级 RAG 语义搜索系统:C++ + ONNX + FAISS 实战
c++·faiss·onnx·rag·语义搜索
星空下的月光影子9 小时前
易语言开发从入门到精通:补充篇·网络爬虫与自动化采集分析系统深度实战·HTTP/HTTPS请求·HTML/JSON解析·反爬策略·电商价格监控·新闻资讯采集
开发语言