Day12-C++20 Coroutines(上):协程原理与Promise/Awaitable机制

C++20 Coroutines(上):协程原理与Promise/Awaitable机制

C++进阶计划 · Day 12 | 预计学习时长:2小时

引言

为什么这个知识点重要

如果说 Day 10 的 Ranges 是 C++20 的"数据处理革命",那么 Coroutines 就是 C++20 的"控制流革命"。协程让开发者可以用同步的方式编写异步代码,彻底改变了我们处理异步 IO、事件驱动编程和流式数据的范式。

对于 7 年 Qt 开发者,你一定用过 QEventLoopQTimer::singleShot、信号槽回调,或者 QtConcurrent::run。这些机制都能工作,但它们都面临同一个根本问题:控制流的碎片化。业务逻辑被切割成回调函数,代码的可读性和可维护性急剧下降。

协程提供了一种优雅的解决方案:让你用"线性"的思维写"非线性"的逻辑。

与前面内容的关联

C++20 协程与多个前面的知识点有紧密联系:

前置知识 在协程中的体现
Lambda 表达式 (Day 4) 协程帧的启动常通过 Lambda 封装
Move 语义 (Day 1) 协程帧的所有权转移
模板元编程 (Day 21-32) Promise/Awaitable 的类型约束
Concepts (Day 9) Awaitable 必须满足特定概念
智能指针 (Day 3) 协程状态的生命周期管理

与 Qt 实战场景的关联

协程在以下 Qt 场景特别有价值:

  • Qt Network :替代 QNetworkReply 的回调地狱,用同步风格处理异步网络请求
  • 数据库操作 :异步数据库查询可以用 co_await 优雅表达
  • QML 集成:在 C++ 层使用协程,不阻塞主线程,同时保持 QML 响应
  • 定时器处理 :用 co_await 替代嵌套的 QTimer::singleShot
  • 文件 IO:异步文件读写

核心概念

1. 协程是什么?------ 与线程的本质区别

在深入 C++20 协程语法之前,必须理解协程的哲学。

1.1 线程:抢占式多任务
复制代码
主线程                    工作线程
┌─────────┐              ┌─────────┐
│  任务A  │              │  任务B  │
│ ██████░░│ ──调度器──>  │ ░░██████│
│ 等待CPU │              │ 等待CPU │
└─────────┘              └─────────┘
     │                        │
     └──────── OS 调度器 ──────┘
              (抢占式切换)

线程由操作系统调度,切换时机不可预测(可能在任何指令处)。每个线程有独立的调用栈(通常 1MB+)。

1.2 协程:协作式多任务
复制代码
┌─────────────────────────────────────┐
│              线程内                  │
│  ┌─────────┐      ┌─────────┐     │
│  │ 协程 A  │      │ 协程 B  │     │
│  │ co_await│ ──>  │ resume  │     │
│  │ 挂起    │      │         │     │
│  └─────────┘      └─────────┘     │
│         │              │           │
│         └── 协程调度器 ─┘          │
│           (协作式切换)              │
└─────────────────────────────────────┘

协程在指定点co_awaitco_yield)主动让出执行权,切换到另一个协程。没有独立的栈,协程帧是堆分配的。

1.3 关键对比
cpp 复制代码
// 线程:切换时机不可控
void threadExample() {
    std::thread t([]{
        doWork();           // 可能在任何时候被抢走CPU
        doMoreWork();       // 无法保证连续执行
    });
    t.join();
}

// 协程:切换时机可控
task<int> coroutineExample() {
    co_await doAsyncWork();     // 主动挂起,让出CPU
    co_return 42;              // 继续从这里执行
}

2. C++20 协程的三驾马车

C++20 定义了三个协程关键字:

关键字 作用 类比
co_await 挂起协程,等待 Awaitable 完成 std::future::get() 但不阻塞线程
co_yield 挂起协程并返回数据(生成器模式) Python 的 yield
co_return 结束协程并返回值 普通函数的 return
cpp 复制代码
// 完整的协程示例
#include <coroutine>
#include <iostream>

// Step 1: 定义 Promise 类型
struct IntPromise {
    int value_;
    
    // 协程开始时调用
    suspend_never initial_suspend() { return {}; }
    
    // 协程结束时调用(正常路径)
    suspend_never final_suspend() noexcept { return {}; }
    
    // co_return 返回值
    void return_value(int v) { value_ = v; }
    
    // 协程未捕获异常时调用
    void unhandled_exception() { 
        std::terminate();
    }
};

// Step 2: 定义协程类型
struct IntCoroutine {
    using promise_type = IntPromise;
    
    std::coroutine_handle<promise_type> handle_;
    
    explicit IntCoroutine(std::coroutine_handle<promise_type> h) : handle_(h) {}
    
    ~IntCoroutine() {
        if (handle_) handle_.destroy();
    }
};

// Step 3: 定义协程函数
IntCoroutine produce() {
    co_return 42;  // 设置返回值
}

int main() {
    auto coroutine = produce();
    
    // 协程已经执行完毕(co_return 是立即执行的)
    std::cout << "Result: " << coroutine.handle_.promise().value_ << "\n";
    // 输出: Result: 42
    
    return 0;
}

3. 协程的执行流程

理解协程的关键是理解其生命周期:

复制代码
协程函数调用 ──> 分配协程帧
                │
                ▼
         ┌─────────────┐
         │  创建期     │
         │ - 分配帧    │
         │ - 构造参数  │
         │ - 调用      │
         │   Promise   │
         │   .get_    │
         │   return_    │
         │   object()    │
         └──────┬──────┘
                │
                ▼
         ┌─────────────┐
         │ 执行期      │
         │ - 运行直到  │
         │   第一个挂起点│
         │ - 挂起/恢复  │
         │ - 处理返回值 │
         └──────┬──────┘
                │
                ▼
         ┌─────────────┐
         │ 销毁期      │
         │ - final_    │
         │   suspend   │
         │ - 清理资源  │
         │ - 释放帧    │
         └─────────────┘

关键洞察 :协程函数体并不是立即执行的。调用协程函数会创建协程帧,但协程体的实际执行可能延迟 到第一个 co_await 表达式求值时。

4. Promise 机制详解

Promise 是协程的"心脏",它定义了协程的行为。

4.1 Promise 必须提供的成员函数
cpp 复制代码
struct MyPromise {
    // 1. get_return_object() - 必须!
    // 从协程创建返回给调用者的对象
    // 返回值类型必须是协程函数的返回类型
    auto get_return_object();
    
    // 2. initial_suspend()
    // 协程开始执行前的挂起点
    // 通常返回 suspend_always{} 立即挂起
    // 或 suspend_never{} 立即开始执行
    [[nodiscard]] auto initial_suspend();
    
    // 3. final_suspend()
    // 协程结束后的挂起点
    // 通常返回 suspend_always{} 让调用者决定何时销毁
    // 或 suspend_never{} 立即销毁
    [[nodiscard]] auto final_suspend() noexcept;
    
    // 4. return_void() 或 return_value(T) - 必须二选一
    // 处理 co_return
    void return_void();
    void return_value(T v);  // 二选一
    
    // 5. unhandled_exception()
    // 协程内未捕获的异常
    void unhandled_exception();
    
    // 注意:以下成员是可选的:
    // - await_transform(expr) - 自定义 co_await 的转换
    // - yield_value(T) - 支持 co_yield
    // - initial/final_suspend 的返回值必须满足 Awaiter 概念
};
4.2 initial_suspend 的选择
cpp 复制代码
// 两种标准 suspend 类型
struct suspend_never {
    constexpr bool await_ready() const noexcept { return true; }  // 不挂起
    constexpr void await_suspend(std::coroutine_handle<>) const noexcept {}
    constexpr void await_resume() const noexcept {}
};

struct suspend_always {
    constexpr bool await_ready() const noexcept { return false; }  // 总是挂起
    constexpr void await_suspend(std::coroutine_handle<>) const noexcept {}
    constexpr void await_resume() const noexcept {}
};

实战选择

cpp 复制代码
// 场景1:惰性启动(推荐)
// 调用者必须显式 resume() 才开始执行
struct LazyPromise {
    auto initial_suspend() { return std::suspend_always{}; }
    auto final_suspend() noexcept { return std::suspend_always{}; }
};
auto task = createCoroutine();  // 协程已创建但未执行
task.resume();  // 现在开始执行

// 场景2:立即执行
// 协程立即开始执行,直到第一个挂起点
struct EagerPromise {
    auto initial_suspend() { return std::suspend_never{}; }
    auto final_suspend() noexcept { return std::suspend_always{}; }
};
auto task = createCoroutine();  // 协程立即开始执行
4.3 Promise 与协程帧的关系
cpp 复制代码
template<typename Promise>
struct std::coroutine_handle<Promise> {
    // 从协程帧中获取 Promise
    Promise& promise() {
        return *static_cast<Promise*>(
            reinterpret_cast<char*>(this) - sizeof(Promise)
            // Promise 通常在协程帧的特定位置
        );
    }
    
    // 判断协程是否完成
    bool done() const;
    
    // 恢复协程执行
    void resume();
    
    // 销毁协程帧
    void destroy();
};

5. Awaitable 与 Awaiter 机制

Awaitable 是可以被 co_await 的类型,Awaiter 是实际处理挂起/恢复的对象。

5.1 Awaiter 必须实现的三个方法
cpp 复制代码
// Awaiter 接口
struct MyAwaiter {
    // 1. await_ready()
    // 如果返回 true:直接继续执行,不挂起
    // 如果返回 false:调用 await_suspend() 并可能挂起
    bool await_ready();
    
    // 2. await_suspend(handle)
    // 挂起协程,handle 是协程句柄
    // 返回 void 或 bool:
    //   - void:总是挂起
    //   - true:挂起
    //   - false:不挂起(立即恢复)
    // 通常在这里安排恢复协程的逻辑
    void/bool await_suspend(std::coroutine_handle<> handle);
    
    // 3. await_resume()
    // 恢复执行后返回的值
    // 这是 co_await 表达式的结果
    auto await_resume();
};
5.2 co_await 的完整执行流程
cpp 复制代码
// co_await expr 的完整展开
{
    // 1. 将 expr 转换为 Awaitable
    auto&& __awaitable = expr;
    
    // 2. 获取或创建 Awaiter
    // 正确的 co_await 展开逻辑(概念版):

	// 1. 获取 Awaitable 对象
	auto&& __awaitable = expr;

	// 2. 获取 Awaiter 对象:
	//    - 如果 __awaitable 有 operator co_await(),调用它
	//    - 否则,直接使用 __awaitable 本身作为 Awaiter
	auto&& __awaiter = 
    	[&]() -> decltype(auto) {
        	if constexpr (requires { __awaitable.operator co_await(); }) {
            	return __awaitable.operator co_await();
        	} else {
            	return static_cast<decltype(__awaitable)>(__awaitable);
        	}
    }();

// 注意:标准中的实现更复杂,涉及协程 Promise 的 await_transform 等
    // 或如果已有 await_ready 等方法,直接使用
    
    // 3. 调用 await_ready()
    if (!__awaiter.await_ready()) {
        // 4a. 挂起协程
        __awaiter.await_suspend(coroutine_handle);
        
        // 此时控制权返回给调用者
        // 协程处于挂起状态
        
        // ... 被恢复后 ...
        
        // 4b. 恢复后清理
    }
    
    // 5. await_resume() 的返回值就是 co_await 的值
    auto __result = __awaiter.await_resume();
}
5.3 自定义 Awaiter 示例
cpp 复制代码
#include <coroutine>
#include <chrono>
#include <iostream>
#include <thread>

// 延迟执行 Awaiter
struct DelayAwaiter {
    std::chrono::milliseconds duration_;
    
    explicit DelayAwaiter(std::chrono::milliseconds d) : duration_(d) {}
    
    // 不立即执行,总是要等待
    bool await_ready() const {
        std::cout << "  [DelayAwaiter] await_ready() = false\n";
        return false;
    }
    
    // 挂起后在新线程中睡眠,然后恢复协程
    void await_suspend(std::coroutine_handle<> handle) {
    std::cout << "  [DelayAwaiter] Suspending, starting thread...\n";
    std::thread([handle, this]() {
        std::this_thread::sleep_for(duration_);
        std::cout << "  [DelayAwaiter] Resuming coroutine...\n";
        
        // ⚠️ 注意:从不同线程恢复协程是允许的,但需要确保:
        // 1. 协程不绑定到特定线程(即不使用线程局部存储)
        // 2. 协程的 Promise/Awaiter 对象是线程安全的
        // 3. 恢复后访问的数据不涉及线程竞争
        
        // 在实际项目中,建议使用 Executor 或 Scheduler 来调度恢复
        handle.resume();  // 从新线程恢复
    }).detach();
}
    
    // 恢复后返回的值
    int await_resume() const {
        std::cout << "  [DelayAwaiter] Resumed!\n";
        return 42;  // co_await 的返回值
    }
};

// 为了更优雅的语法,提供 operator co_await
auto operator co_await(std::chrono::milliseconds duration) {
    return DelayAwaiter{duration};
}

// 使用协程
std::coroutine_handle<> delayedCoroutine() {
    std::cout << "Coroutine started\n";
    
    co_await std::chrono::milliseconds(100);
    
    std::cout << "Coroutine resumed after delay\n";
    
    co_return;
}

int main() {
    auto handle = delayedCoroutine();
    
    std::cout << "Main: waiting...\n";
    
    // 在 resume 之前,协程已经挂起
    // handle.resume();  // 如果 DelayAwaiter 不自己恢复
    
    // 由于 DelayAwaiter 内部启动了线程并自己恢复
    // 我们只需要等待协程完成
    // 这里简单等待一下
    std::this_thread::sleep_for(std::chrono::milliseconds(200));
    
    // 清理
    if (!handle.done()) {
        handle.resume();
    }
    handle.destroy();
    
    return 0;
}

6. Generator:协程最常见的应用

Generator 是 co_yield 的典型应用,它惰性生成一系列值。

6.1 手写 Generator 的完整实现
cpp 复制代码
#include <coroutine>
#include <type_traits>
#include <exception>
#include <cstddef>

template<typename T>
class Generator {
public:
    // Promise 类型
    struct Promise {
        std::optional<T> value_;  // co_yield 的值
        std::exception_ptr exception_;
        bool isCompleted_ = false;
        
        auto get_return_object() {
            return Generator{std::coroutine_handle<Promise>::from_promise(*this)};
        }
        
        auto initial_suspend() {
            return std::suspend_always{};  // 惰性启动
        }
        
        auto final_suspend() noexcept {
            return std::suspend_always{};
        }
        
        // co_yield value 等价于 co_await yield_value(value)
        std::suspend_always yield_value(T&& v) {
            value_ = std::forward<T>(v);
            return {};
        }
        
        std::suspend_always yield_value(const T& v) {
            value_ = v;
            return {};
        }
        
        void return_void() {
            isCompleted_ = true;
        }
        
        void unhandled_exception() {
            exception_ = std::current_exception();
        }
        
        T value() {
            if (exception_) {
                std::rethrow_exception(exception_);
            }
            return std::move(*value_);
        }
    };
    
    using handle_type = std::coroutine_handle<Promise>;
    
private:
    handle_type handle_;
    
public:
    explicit Generator(handle_type h) : handle_(h) {}
    
    Generator(Generator&& other) noexcept : handle_(other.handle_) {
        other.handle_ = nullptr;
    }
    
    Generator& operator=(Generator&& other) noexcept {
        if (this != &other) {
            if (handle_) handle_.destroy();
            handle_ = other.handle_;
            other.handle_ = nullptr;
        }
        return *this;
    }
    
    ~Generator() {
        if (handle_) handle_.destroy();
    }
    
    // Range 支持
    class Iterator {
        handle_type* handle_;
    public:
        using iterator_category = std::input_iterator_tag;
        using value_type = T;
        using difference_type = std::ptrdiff_t;
        using pointer = T*;
        using reference = T&;
        
        explicit Iterator(handle_type* h, bool end = false) : handle_(h) {
            if (!end && handle_ && !handle_->done()) {
                // 不需要额外的 resume,initial_suspend 已经挂起
            }
        }
        
       // 更好的实现:返回 const 引用,避免拷贝
	const T& operator*() const {
    	return handle_->promise().value();
	}

	// 如果 T 是资源密集型类型(如 std::string),引用版本更高效
	// 但如果 T 是基本类型,拷贝与引用差别不大

	// 在迭代器中,value_type 应该与 operator* 的返回类型一致
	using value_type = T;        // 值类型
	using reference = const T&;  // 引用类型
        
        Iterator& operator++() {
            handle_->resume();
            if (handle_->done()) {
                handle_ = nullptr;
            }
            return *this;
        }
        
        // 后缀 ++ 应该返回旧值
	Iterator operator++(int) {
    	Iterator temp = *this;  // 保存旧状态
    	++(*this);              // 前进
    	return temp;            // 返回旧值
	}

// 注意:对于 input_iterator,后缀 ++ 的返回值可能不被严格要求
// 但标准库中大多数算法需要它可用
        
        bool operator==(const Iterator& other) const {
            return handle_ == other.handle_;
        }
        
        bool operator!=(const Iterator& other) const {
            return !(*this == other);
        }
    };
    
    Iterator begin() {
        if (handle_) {
            handle_.resume();  // 开始执行到第一个 yield
        }
        return Iterator{handle_};
    }
    
    Iterator end() {
        return Iterator{nullptr, true};
    }
    
    explicit operator bool() const {
        return handle_ && !handle_.done();
    }
    
    T operator()() {
        handle_.resume();
        return handle_.promise().value();
    }
};
6.2 Generator 使用示例
cpp 复制代码
// 生成斐波那契数列
Generator<int> fibonacci() {
    co_yield 0;  // F(0)
    co_yield 1;  // F(1)
    
    int a = 0, b = 1;
    while (true) {
        co_yield a + b;  // F(n) = F(n-1) + F(n-2)
        int next = a + b;
        a = b;
        b = next;
    }
}

// 生成范围内整数
// 注意:此处的 Generator 类型需要在文件前面有完整定义
// 确保 Generator<int> 的 Promise 类型实现了 yield_value

// 如果 Generator 定义中只支持 co_yield 值类型,
// 那么 range 的 step 参数使用默认值 1 是可以的

Generator<int> range(int start, int end, int step = 1) {
    for (int i = start; i < end; i += step) {
        co_yield i;
    }
}

// ⚠️ 注意:如果 step 为 0,会导致无限循环
// 建议添加参数校验:
Generator<int> range_safe(int start, int end, int step = 1) {
    if (step == 0) {
        co_return;  // step 为 0 时提前结束
    }
    if (step > 0) {
        for (int i = start; i < end; i += step) {
            co_yield i;
        }
    } else {
        for (int i = start; i > end; i += step) {
            co_yield i;
        }
    }
}

// 生成质数
Generator<int> primes(int maxValue) {
    for (int n = 2; n <= maxValue; ++n) {
        bool isPrime = true;
        for (int i = 2; i * i <= n; ++i) {
            if (n % i == 0) {
                isPrime = false;
                break;
            }
        }
        if (isPrime) {
            co_yield n;
        }
    }
}

// 使用示例
int main() {
    std::cout << "First 10 Fibonacci numbers:\n";
    auto fib = fibonacci();
    for (int i = 0; i < 10; ++i) {
        std::cout << fib() << " ";  // 0 1 1 2 3 5 8 13 21 34
    }
    std::cout << "\n";
    
    std::cout << "Even numbers 0-10:\n";
    for (int n : range(0, 10, 2)) {
        std::cout << n << " ";  // 0 2 4 6 8
    }
    std::cout << "\n";
    
    std::cout << "Primes up to 50:\n";
    for (int p : primes(50)) {
        std::cout << p << " ";  // 2 3 5 7 11 13 17 19 23 29 31 37 41 43 47
    }
    std::cout << "\n";
    
    return 0;
}

逐行解读 Generator 实现

cpp 复制代码
// 核心 1:Promise 存储 yield 的值
struct Promise {
    std::optional<T> value_;  // co_yield 会设置这个值
    
    // co_yield value 的展开:
    // 1. 调用 promise.yield_value(value)
    // 2. promise 保存 value 到 value_
    // 3. 协程挂起
    // 4. 调用者通过 promise.value() 获取值
    
    std::suspend_always yield_value(T&& v) {
        value_ = std::forward<T>(v);  // 保存值
        return {};                    // 挂起协程
    }
};

// 核心 2:迭代器恢复协程获取下一个值
Iterator& operator++() {
    handle_->resume();           // 恢复协程执行到下一个 co_yield
    if (handle_->done()) {
        handle_ = nullptr;       // 协程结束
    }
    return *this;
}

// 核心 3:begin() 开始执行
Iterator begin() {
    if (handle_) {
        handle_.resume();  // 第一次 resume:执行到第一个 co_yield
    }
    return Iterator{handle_};
}

代码实战

实战 1:异步任务框架 Task

这是 C++20 协程最常见的应用模式------实现类似 std::future 但使用协程的 Task<T>

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

// 前向声明
template<typename T>
struct Task;

// 简化的线程池
class ThreadPool {
public:
    static ThreadPool& instance() {
    	static ThreadPool pool;  // ✅ 线程安全的单例
    	return pool;
	}
// 但需要确保在程序退出时正确停止所有线程
// 推荐:在 main 退出前调用 pool.stop()
    
    void schedule(std::coroutine_handle<> handle) {
        std::lock_guard<std::mutex> lock(mutex_);
        tasks_.push(handle);
        cv_.notify_one();
    }
    
    void run() {
    while (running_) {  // ✅ 添加退出条件
        std::coroutine_handle<> task;
        {
            std::unique_lock<std::mutex> lock(mutex_);
            cv_.wait(lock, [this] { return !tasks_.empty() || !running_; });
            if (!running_) break;
            task = tasks_.front();
            tasks_.pop();
        }
        task.resume();  // ✅ 使用 resume() 而非 operator()
    }
}

void stop() {
    running_ = false;
    cv_.notify_all();
}

private:
    std::atomic<bool> running_{true};
    
private:
    std::mutex mutex_;
    std::condition_variable cv_;
    std::queue<std::coroutine_handle<>> tasks_;
    
    ThreadPool() = default;
};

// Task 的 Promise
template<typename T>
struct TaskPromise {
    std::coroutine_handle<> continuation_;  // 完成后恢复的协程
    std::variant<T, std::exception_ptr> result_;
    
    auto get_return_object() {
        return Task<T>{std::coroutine_handle<TaskPromise>::from_promise(*this)};
    }
    
    // 立即开始执行
    auto initial_suspend() { return std::suspend_never{}; }
    
    // 完成后挂起(让 continuation 决定何时销毁)
    auto final_suspend() noexcept {
        struct Awaiter {
            std::coroutine_handle<> continuation;
            
            bool await_ready() const noexcept { return false; }
            
            void await_suspend(std::coroutine_handle<>) const {
                // 恢复等待此 Task 的协程
                if (continuation) {
                    continuation.resume();
                }
            }
            
            void await_resume() const noexcept {}
        };
        
        return Awaiter{continuation_};
    }
    
    void return_value(T&& value) {
    result_.template emplace<1>(std::move(value));  // ✅ T 在索引 1
	}

	void unhandled_exception() {
    	result_.template emplace<0>(std::current_exception());  // ✅ exception_ptr 在索引 0
	}
    
    T result() {
        if (result_.index() == 1) {
            std::rethrow_exception(std::get<1>(result_));
        }
        return std::get<0>(std::move(result_));
    }
};

// Task 类型
template<typename T>
struct Task {
    using promise_type = TaskPromise<T>;
    
private:
    std::coroutine_handle<promise_type> handle_;
    
public:
    explicit Task(std::coroutine_handle<promise_type> h) : handle_(h) {}
    
    ~Task() {
        if (handle_) handle_.destroy();
    }
    
    // Task 本身就是 Awaitable
    struct Awaiter {
    std::coroutine_handle<promise_type> handle;  // 成员名是 handle
    
    bool await_ready() const {
        return handle.done();  // ✅ 使用 handle
    }
    
    void await_suspend(std::coroutine_handle<> cont) {
        handle.promise().continuation_ = cont;  // ✅ 使用 handle
        ThreadPool::instance().schedule(handle);  // ✅ 使用 handle
    }
    
    T await_resume() {
        return handle.promise().result();  // ✅ 使用 handle
    }
};
    
    Awaiter operator co_await() const {
        return Awaiter{handle_};
    }
};

// 便捷函数
template<typename T>
Task<T> scheduleOnThreadPool(T value) {
    co_return value;
}

// 示例:异步加法
Task<int> asyncAdd(int a, int b) {
    // 模拟异步操作
    std::cout << "  [asyncAdd] Starting async computation...\n";
    co_await std::suspend_always{};  // 模拟挂起
    std::cout << "  [asyncAdd] Resumed, computing...\n";
    co_return a + b;
}

// 示例:异步链式调用
Task<int> computePipeline(int x) {
    std::cout << "  [Pipeline] Starting with " << x << "\n";
    
    int a = co_await asyncAdd(x, 10);
    std::cout << "  [Pipeline] Got " << a << ", adding 20...\n";
    
    int b = co_await asyncAdd(a, 20);
    std::cout << "  [Pipeline] Got " << b << ", adding 30...\n";
    
    int c = co_await asyncAdd(b, 30);
    std::cout << "  [Pipeline] Final result: " << c << "\n";
    
    co_return c;
}

// 主协程
Task<void> mainTask() {
    std::cout << "[Main] Starting pipeline...\n";
    int result = co_await computePipeline(5);
    std::cout << "[Main] Final result: " << result << "\n";
    co_return;
}

int main() {
    auto handle = mainTask().handle_;
    
    // 简单的事件循环
    std::thread pool([]{ ThreadPool::instance().run(); });
    
    while (!handle.done()) {
        handle.resume();
        std::this_thread::sleep_for(std::chrono::milliseconds(10));
    }
    
    handle.destroy();
    
    // 注意:这个例子为了简化没有完全正确地实现线程池
    // 真实项目中建议使用成熟的库如 cppcoro 或 libunifex
    
    return 0;
}

实战 2:协程替代 Qt 回调

这是一个更贴近 Qt 实战的例子,展示如何用协程替代 QNetworkReply 的回调。

cpp 复制代码
#include <QCoreApplication>
#include <QNetworkAccessManager>
#include <QNetworkRequest>
#include <QNetworkReply>
#include <QByteArray>
#include <coroutine>
#include <optional>

// Qt 的 Awaitable 封装
struct NetworkReplyAwaiter {
    QNetworkReply* reply_;
    std::coroutine_handle<> continuation_;
    
    explicit NetworkReplyAwaiter(QNetworkReply* reply) 
        : reply_(reply) {}
    
    bool await_ready() const {
        return reply_->isFinished();
    }
    
    void await_suspend(std::coroutine_handle<> cont) {
        continuation_ = cont;
        // ✅ 不阻塞事件循环,使用信号槽异步恢复
        QObject::connect(reply_, &QNetworkReply::finished, 
            reply_, [this]() {
                // 在 Qt 事件循环中恢复协程
                continuation_.resume();
            }, Qt::QueuedConnection);  // 使用队列连接保证线程安全
    }
    
    QNetworkReply* await_resume() {
        return reply_;
    }
};

// Awaitable 工厂函数
// ✅ 更好的实现:不阻塞主线程
auto waitForReadyRead(QNetworkReply* reply) {
    struct Awaiter {
        QNetworkReply* reply_;
        std::coroutine_handle<> continuation_;
        
        bool await_ready() const { return false; }
        
        void await_suspend(std::coroutine_handle<> cont) {
            continuation_ = cont;
            QObject::connect(reply_, &QNetworkReply::readyRead,
                reply_, [this]() {
                    continuation_.resume();
                }, Qt::QueuedConnection);
        }
        
        QByteArray await_resume() { return reply_->readAll(); }
    };
    return Awaiter{reply_};
}

// Task 类型(简化版)
template<typename T>
struct QtTask {
    struct promise_type {
        std::coroutine_handle<> continuation_;
        std::variant<T, std::exception_ptr> result_;
        
        QtTask get_return_object() {
            return QtTask{std::coroutine_handle<promise_type>::from_promise(*this)};
        }
        
        auto initial_suspend() { return std::suspend_always{}; }
        
        auto final_suspend() noexcept {
            return std::suspend_never{};
        }
        
        void return_value(T&& v) { result_.template emplace<0>(std::move(v)); }
        void unhandled_exception() { result_.template emplace<1>(std::current_exception()); }
        
        T value() {
            if (result_.index() == 1) {
                std::rethrow_exception(std::get<1>(result_));
            }
            return std::get<0>(std::move(result_));
        }
    };
    
private:
    std::coroutine_handle<promise_type> handle_;
    
public:
    explicit QtTask(std::coroutine_handle<promise_type> h) : handle_(h) {}
    ~QtTask() { if (handle_) handle_.destroy(); }
    
    struct Awaiter {
        std::coroutine_handle<promise_type> handle;
        bool await_ready() const { return handle.done(); }
        void await_suspend(std::coroutine_handle<>) { /* 实际应该用调度器 */ }
        auto await_resume() { return handle.promise().value(); }
    };
    
    Awaiter operator co_await() { return Awaiter{handle_}; }
};

// 异步获取网页(用协程风格)
QtTask<QByteArray> fetchUrl(QNetworkAccessManager& nam, const QUrl& url) {
    QNetworkRequest request(url);
    QNetworkReply* reply = nam.get(request);
    
    // co_await 等待网络请求完成
    co_await NetworkReplyAwaiter(reply);
    
    // 检查错误
    if (reply->error() != QNetworkReply::NoError) {
        qWarning() << "Network error:" << reply->errorString();
        reply->deleteLater();
        co_return QByteArray();
    }
    
    // 读取数据
    QByteArray data = reply->readAll();
    reply->deleteLater();
    
    co_return data;
}

// 主协程:链式请求
QtTask<void> fetchMultipleUrls(QNetworkAccessManager& nam) {
    std::vector<QUrl> urls = {
        QUrl("https://example.com"),
        QUrl("https://example.org"),
        QUrl("https://example.net"),
    };
    
    for (const QUrl& url : urls) {
        qDebug() << "Fetching:" << url.toString();
        
        QByteArray data = co_await fetchUrl(nam, url);
        
        qDebug() << "Got" << data.size() << "bytes from" << url.host();
    }
    
    qDebug() << "All done!";
    co_return;
}

// 在 Qt 中使用
int main(int argc, char* argv[]) {
    QCoreApplication app(argc, argv);
    
    QNetworkAccessManager nam;
    
    // 创建协程
    auto task = fetchMultipleUrls(nam);
    
    // 处理事件循环直到协程完成
    // 由于我们的 Task 是简化的,需要额外的事件处理
    // 真实项目中 Task 会管理协程的生命周期
    
    QTimer::singleShot(3000, [&]() {
        qDebug() << "Timeout";
        app.quit();
    });
    
    return app.exec();
}

关键洞察

cpp 复制代码
// 传统回调风格(嵌套地狱)
void fetchOldStyle() {
    nam.get(request, [&](QNetworkReply* reply) {
        if (reply->error()) { handleError(reply); return; }
        reply->readAll();
        // 下一个请求...
    });
}

// 协程风格(线性可读)
void fetchCoroutineStyle() {
    auto data = co_await fetchUrl(nam, url);  // 看起来是同步的
    // 下一个请求...
}

实战 3:流式数据处理

协程非常适合处理流式数据,比如文件行处理:

cpp 复制代码
#include <coroutine>
#include <fstream>
#include <string>
#include <vector>
#include <optional>

// 行生成器
class LineReader {
public:
    struct Promise {
        std::optional<std::string> line_;
        std::coroutine_handle<> continuation_;
        
        auto get_return_object() {
            return LineReader{std::coroutine_handle<Promise>::from_promise(*this)};
        }
        
        auto initial_suspend() { return std::suspend_always{}; }
        auto final_suspend() noexcept { return std::suspend_never{}; }
        
        auto yield_value(const std::string& line) {
            line_ = line;
            return std::suspend_always{};
        }
        
        void return_void() { line_ = std::nullopt; }
        void unhandled_exception() { throw; }
        
        std::string value() { return std::move(*line_); }
    };
    
private:
    std::coroutine_handle<Promise> handle_;
    
public:
    using promise_type = Promise;
    
    explicit LineReader(std::coroutine_handle<Promise> h) : handle_(h) {}
    
    ~LineReader() { if (handle_) handle_.destroy(); }
    
    class Iterator {
        std::coroutine_handle<Promise>* handle_;
        bool is_end_;
    public:
        using iterator_category = std::input_iterator_tag;
        using value_type = std::string;
        using difference_type = std::ptrdiff_t;
        
        Iterator(std::coroutine_handle<Promise>* h, bool end = true)
        : handle_(h), is_end_(end) {
        if (!is_end_ && handle_ && *handle_) {
            // 确保协程在第一个 yield 处挂起
        }
    }
        
        std::string operator*() const {
            return handle_->promise().value();
        }
        
        Iterator& operator++() {
            if (handle_) {
                handle_->resume();
                if (handle_->done()) {
                    handle_ = nullptr;
                }
            }
            return *this;
        }
        
        bool operator==(const Iterator& other) const {
            return handle_ == other.handle_;
        }
        
        bool operator!=(const Iterator& other) const {
            return !(*this == other);
        }
    };
    
    Iterator begin() {
        if (handle_) handle_->resume();
        return Iterator{handle_};
    }
    
    Iterator end() { return Iterator{nullptr, true}; }
    
    explicit operator bool() const {
        return handle_ && !handle_->done();
    }
};

// 协程函数:读取文件行
LineReader readLines(const std::string& filename) {
    std::ifstream file(filename);
    if (!file.is_open()) {
        co_return;  // 提前结束
    }
    
    std::string line;
    while (std::getline(file, line)) {
        co_yield line;  // 生成一行
    }
    // 到达文件末尾,隐式 co_return
}

// 处理协程:过滤和转换
LineReader filterLines(LineReader input, 
    std::function<bool(const std::string&)> predicate) {
    for (const std::string& line : input) {
        if (predicate(line)) {
            co_yield line;
        }
    }
}

// 示例使用
int main() {
    // 读取并处理文件
    auto lines = readLines("example.txt");
    
    // 链式处理:过滤空行,转换为大写
    for (const std::string& line : lines) {
        if (!line.empty()) {
            std::string upper = line;
            for (char& c : upper) {
                c = std::toupper(c);
            }
            std::cout << upper << "\n";
        }
    }
    
    // 更优雅的链式处理
    auto filtered = filterLines(
        readLines("example.txt"),
        [](const std::string& line) { return !line.empty() && line[0] != '#'; }
    );
    
    return 0;
}

常见陷阱与最佳实践

陷阱 1:协程帧内存泄漏

cpp 复制代码
// ❌ 危险:协程句柄未正确销毁
Task<void> leakyCoroutine() {
    auto nested = createOtherTask();  // 协程被创建
    co_await nested;  // 如果这里抛异常,nested 的协程句柄泄漏
    co_return;
}

// ✅ 正确:使用 RAII 管理
class ScopedTask {
    std::coroutine_handle<> handle_;
public:
    explicit ScopedTask(std::coroutine_handle<> h) : handle_(h) {}
    ~ScopedTask() { 
        if (handle_) {
            if (!handle_.done()) {
                handle_.resume();  // 先尝试完成
            }
            handle_.destroy();
        }
    }
    
    // 移动构造函数
    ScopedTask(ScopedTask&& other) noexcept : handle_(other.handle_) {
        other.handle_ = nullptr;
    }
    
    ScopedTask& operator=(ScopedTask&& other) noexcept {
        if (this != &other) {
            if (handle_) handle_.destroy();
            handle_ = other.handle_;
            other.handle_ = nullptr;
        }
        return *this;
    }
    
    // 禁用拷贝
    ScopedTask(const ScopedTask&) = delete;
    ScopedTask& operator=(const ScopedTask&) = delete;
    
    // 添加移动支持
    explicit operator bool() const { return handle_ && !handle_.done(); }
};

Task<void> safeCoroutine() {
    ScopedTask nested(createOtherTask());
    co_await nested;
    co_return;
}

陷阱 2:引用悬垂

cpp 复制代码
// ❌ 危险:返回指向局部变量的协程
Generator<int> badGenerator() {
    int local = 42;
    co_yield local;  // co_yield 会复制值,但这里可能有其他问题
    
    std::string s = "hello";
    co_yield s.length();  // OK,co_yield 复制值
    
    // 如果 Generator 保存引用而不是值...
}

// ✅ 正确:确保值被正确捕获
Generator<int> goodGenerator() {
    int local = 42;
    co_yield local;  // 复制
    co_return;
}

陷阱 3:在析构函数中调用 co_await

cpp 复制代码
// ❌ 编译错误:析构函数不能是协程
struct Bad {
    ~Bad() {
        co_return;  // 错误!
    }
};

// ✅ 正确:在析构函数中清理协程资源
struct Good {
    std::coroutine_handle<> handle_;
    
    ~Good() {
        if (handle_ && !handle_.done()) {
            handle_.resume();  // 先完成
        }
        if (handle_) {
            handle_.destroy();  // 再销毁
        }
    }
};

陷阱 4:忘记处理异常

cpp 复制代码
// ❌ 危险:异常被 std::terminate 捕获
Task<void> badCoroutine() {
    throw std::runtime_error("oops");  // 未捕获会导致 terminate
    co_return;
}

// ✅ 正确:使用 try-catch 或让 Promise 处理
Task<void> goodCoroutine() {
    try {
        co_await someAsyncOperation();  // 可能抛出
        co_return;
    } catch (const std::exception& e) {
        // 记录或处理异常
        std::cerr << "Exception: " << e.what() << "\n";
        co_return;
    }
}

陷阱 5:协程中的死锁

cpp 复制代码
// ❌ 危险:在持有锁时挂起
std::mutex mtx;
Task<void> deadlockingCoroutine() {
    std::lock_guard<std::mutex> lock(mtx);
    co_await asyncOperation();  // 挂起,但锁仍持有!
    // 其他协程等待这个锁 -> 死锁
    co_return;
}

// ✅ 正确:使用锁管理器自动释放
std::mutex mtx;
Task<void> safeCoroutine() {
    co_await [this]() -> Task<void> {
        std::lock_guard<std::mutex> lock(mtx);
        co_await asyncOperation();  // 现在安全了
    }();
    co_return;
}

// ✅ 更好:使用 Qt 的信号量或其他异步锁

最佳实践 1:使用成熟的协程库

cpp 复制代码
// C++20 标准库的协程支持非常底层
// 实际项目建议使用成熟库:

// 1. cppcorot (Lewis Baker)
// https://github.com/andreasbuhr/cppcoro
#include <cppcoro/Task.hpp>
#include <cppcoro/schedule_on.hpp>

cppcoro::task<int> myTask() {
    co_return 42;
}

// 2. libunifex (Meta/Facebook)
// https://github.com/facebookexperimental/libunifex
#include <unifex/single_thread_context.hpp>

// 3. std::execution (C++23 提案)
// https://wg21.link/p2300

最佳实践 2:Promise 类型的设计模式

cpp 复制代码
// 推荐:让你的 Task/Promise 满足标准 Concepts

// std::suspend_always / std::suspend_never 的选择原则:
// - initial_suspend: 几乎总是 suspend_always(惰性)
// - final_suspend: suspend_always(让调用者控制销毁时机)

// 推荐的任务状态机:
struct TaskPromise {
    enum class State { Created, Running, Suspended, Completed };
    State state_ = State::Created;
    
    auto initial_suspend() {
        state_ = State::Suspended;
        return std::suspend_always{};
    }
    
    auto final_suspend() noexcept {
        state_ = State::Completed;
        return std::suspend_always{};
    }
};

最佳实践 3:调试协程

cpp 复制代码
// ✅ 添加日志的技巧
template<typename T>
struct DebugTask {
    struct promise_type {
        auto get_return_object() {
            std::cerr << "[Debug] Task created\n";
            return DebugTask{std::coroutine_handle<promise_type>::from_promise(*this)};
        }
        
        auto initial_suspend() {
            std::cerr << "[Debug] Suspended at start\n";
            return std::suspend_always{};
        }
        
        auto final_suspend() noexcept {
            std::cerr << "[Debug] Completed\n";
            return std::suspend_always{};
        }
        
        // ... 其他方法
    };
    // ...
};

进阶思考

1. C++20 协程 vs 其他语言的协程

cpp 复制代码
// Python 协程
async def python_coroutine():
    result = await async_function()
    return result

// JavaScript 协程 (Promise 风格)
async function jsCoroutine() {
    const result = await asyncFunction();
    return result;
}

// C++20 协程
Task<int> cppCoroutine() {
    int result = co_await asyncFunction();
    co_return result;
}

// 关键区别:
// - Python/JS: 运行时(async/await)是语言内置的
// - C++20: 协程是库可扩展的,await 是语法糖展开为 Awaiter
特性 Python/JavaScript C++20
async/await 实现 运行时内置 库可扩展(通过 Promise/Awaiter)
调度器 内置于运行时 用户自定义(通过 Executor/Scheduler)
堆内存分配 运行时自动管理 用户可控(可自定义分配器)
异常处理 运行时传播 用户自定义(通过 unhandled_exception)
性能 受限于运行时 零开销抽象(编译期生成)
调试支持 成熟(框架内置) 较弱(需手动打日志或使用工具)

2. 协程与 Executor/Scheduler

真正的异步系统需要协程与执行器的结合:

cpp 复制代码
// 执行器概念
struct Executor {
    virtual ~Executor() = default;
    virtual void schedule(std::coroutine_handle<>) = 0;
};

// 示例:线程池执行器
class ThreadPoolExecutor : public Executor {
    std::vector<std::thread> threads_;
    std::queue<std::coroutine_handle<>> tasks_;
    std::mutex mutex_;
    std::condition_variable cv_;
    
public:
    void schedule(std::coroutine_handle<> handle) override {
        {
            std::lock_guard lock(mutex_);
            tasks_.push(handle);
        }
        cv_.notify_one();
    }
    
    // ... 启动和管理线程池
};

// 使用执行器的 Task
template<typename T>
struct ScheduledTask {
    struct promise_type {
        Executor* executor_;
        
        ScheduledTask get_return_object() { ... }
        auto initial_suspend() { return std::suspend_always{}; }
        auto final_suspend() {
            return Awaiter{executor_};  // 在执行器上恢复
        }
        // ...
    };
};

3. 协程与模板元编程的结合

cpp 复制代码
// 泛型协程工厂
template<std::ranges::range R>
Generator<std::ranges::range_value_t<R>> toGenerator(R&& range) {
    for (auto&& elem : range) {
        co_yield std::forward<decltype(elem)>(elem);
    }
}

// Concept 约束的协程
template<typename T>
concept Suspendable = requires(T t) {
    t.initial_suspend();
    t.final_suspend();
};

template<Suspendable S>
struct SuspendedTask {
    // ...
};

4. C++23 及未来的协程演进

cpp 复制代码
// C++23 std::expected 与协程结合
std::coroutine_handle<> 
    maybe_suspend(std::expected<int, std::error_code> exp) {
    if (!exp) {
        co_return;  // 处理错误
    }
    co_return exp.value();
}

// C++26 可能的标准库协程支持
// https://wg21.link/p2542 - std::generator
// 类似于本文的 Generator 实现

参考资源

标准文档

权威书籍

  • 《C++20 Coroutines》 - Andreas Weiss (协程领域先驱)
  • 《C++ High Performance》 - 包含协程性能分析章节
  • 《The C++ Standard Library》 - Nicolai Josuttis - 协程章节

高质量博客与演讲

实战库

Qt 集成


下期预告:Day 13 - C++20 Coroutines(下):手写完整 Generator、异步 IO 协程封装、协程调度器设计、与回调/Promise 的深度对比分析

相关推荐
01二进制代码漫游日记44 分钟前
C++基础入门速通
java·开发语言·c++
Herbert_hwt1 小时前
第六章 Java深入理解接口、函数式接口与lambda表达式
java·开发语言·算法
橙橙笔记1 小时前
C++的学习第三部分
开发语言·c++·学习
2603_9651481110 小时前
如何解析JSON数据?API返回的商品信息处理教程
开发语言·数据库·python·自动化·json·api
知无不研12 小时前
lambda表达式的使用(3)
开发语言·c++·lambda
cfm_291413 小时前
SpringAI + Ollama 本地大模型
java·开发语言·人工智能·语言模型
C++ 老炮儿的技术栈14 小时前
基于Qt实现轻量化本地音乐播放器
开发语言·c++·qt·c·播放器·音乐
qq_4480111614 小时前
C语言中的柔性数组
c语言·开发语言·柔性数组
小白学大数据14 小时前
长周期爬虫的数据一致性:断点续爬 + 事务回滚保障采集质量
开发语言·爬虫·测试工具