C++20 Coroutines(上):协程原理与Promise/Awaitable机制
C++进阶计划 · Day 12 | 预计学习时长:2小时
引言
为什么这个知识点重要
如果说 Day 10 的 Ranges 是 C++20 的"数据处理革命",那么 Coroutines 就是 C++20 的"控制流革命"。协程让开发者可以用同步的方式编写异步代码,彻底改变了我们处理异步 IO、事件驱动编程和流式数据的范式。
对于 7 年 Qt 开发者,你一定用过 QEventLoop、QTimer::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_await、co_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 - 协程章节
高质量博客与演讲
- Lewis Baker's Blog - cppcoro 库作者,协程机制深度解析
- Gor Nishanov's Blog - 微软 C++ 协程设计者
- Klaus Iglberger's Coroutine Talks - CppCon 演讲
- Rainer Grimm's Modernes C++ - 协程系列文章
实战库
Qt 集成
- Qt Asynchronous C++ - Qt 官方的协程支持
- QtnProperty - Qt Promise 库
下期预告:Day 13 - C++20 Coroutines(下):手写完整 Generator、异步 IO 协程封装、协程调度器设计、与回调/Promise 的深度对比分析