C++协程切换的机制基于如下C++协程标准的规定:await_suspend如果直接返回一个coroutine_handle协程句柄。那么被返回的句柄会立即恢复,即调用返回coroutine_handle的resume()方法
查看如下例子:
#include <iostream>
#include <coroutine>
#include <string>
// 前向声明
struct Task;
// 一个简单的 Awaiter,用于触发协程切换
struct SwitchTo {
std::coroutine_handle<> target_coro;
bool await_ready() const noexcept {
return false;
}
std::coroutine_handle<> await_suspend(std::coroutine_handle<> /*current_coro*/) const noexcept {
std::cout << " [Awaiter] Suspending current coroutine, switching to target.\n";
return target_coro;
}
void await_resume() const noexcept {
std::cout << " [Awaiter] Resumed original coroutine.\n";
}
};
// 一个专门用于获取当前协程句柄的 Awaiter
struct GetCurrentHandle {
bool await_ready() const noexcept { return false; }
bool await_suspend(std::coroutine_handle<> h) noexcept {
handle = h;
return false; // 关键:返回 false,表示不挂起,立即恢复
}
std::coroutine_handle<> await_resume() const noexcept {
return handle;
}
private:
std::coroutine_handle<> handle;
};
// 一个简单的协程 Task 类型
struct Task {
struct promise_type {
Task get_return_object() { return { std::coroutine_handle<promise_type>::from_promise(*this) }; }
std::suspend_always initial_suspend() noexcept { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
void return_void() {}
void unhandled_exception() {}
};
std::coroutine_handle<promise_type> handle;
~Task() {
if (handle) {
handle.destroy();
}
}
};
// 前向声明
Task coro_B(std::coroutine_handle<> caller);
// 第一个协程
Task coro_A() {
std::cout << "[Coro A] Started.\n";
// 正确的方式:co_await 一个特制的 awaiter 来获取自己的句柄
auto self_handle = co_await GetCurrentHandle{};
// 创建 coro_B,并把自己的句柄传给它
std::cout << "[Coro A] Creating Coro B.\n";
Task b = coro_B(self_handle);
std::cout << "[Coro A] About to switch to Coro B.\n";
co_await SwitchTo{ b.handle }; // <--- 切换到 coro_B
std::cout << "[Coro A] Back from Coro B.\n";
std::cout << "[Coro A] Finished.\n";
}
// 第二个协程
Task coro_B(std::coroutine_handle<> caller_handle) {
std::cout << " [Coro B] Started.\n";
std::cout << " [Coro B] About to switch back to Coro A.\n";
co_await SwitchTo{ caller_handle }; // <--- 切换回调用者 (coro_A)
std::cout << " [Coro B] This line will not be printed because we never resume B.\n";
}
int main() {
std::cout << "Main: Starting Coro A.\n";
Task a = coro_A();
std::cout << "Main: Resuming Coro A to run.\n";
a.handle.resume(); // 启动 coro_A
std::cout << "Main: Coro A handle is done: " << std::boolalpha << a.handle.done() << "\n";
// Task 的析构函数会自动清理句柄
std::cout << "Main: Finished.\n";
return 0;
}
GetCurrentHandle是一个特别的awaiter,通过协程自动传递coroutine_handle给await_suspend的机制,我们得到了promise_type对应的coroutine_handle,并且通过await_suspend return false继续执行await_resume的机制,我们直接可以将handle返回给co_await表达式。
执行另一个协程函数,其挂起时我们可以得到外层类对象Task 。理论上说,如果Task的promise_type::initial_suspend返回的awaitable不挂起的话,我们直接就可以执行协程B了。这里是为了演示如何通过awaiter的await_suspend来实现协程的切换。
SwitchTo 类也是一个awaiter,它包含了一个目标协程句柄的成员变量,在co_await时,通过传入一个协程句柄初始化该变量,并且await_ready总是返回false,此时进入await_suspend,返回刚刚初始化的句柄,于是根据C++协程标准规定,会继续执行目标句柄对应的协程,即调用该句柄的resume方法,此时目标句柄继续执行,当前句柄(即await_suspend参数传进来的句柄)暂停执行。
由于我们在协程B传入了协程A的句柄,所以在适当的时候,我们可以通过同样的方法来切换为协程A的执行。
最后,我们要尤其注意内存泄露的问题,这里我们通过RAII的方式管理内存。当协程A执行完毕,Task b变量析构,其handle被释放,避免了内存泄漏,此时协程b的handle.done()返回false,即没有执行完成,但也需要释放,否则后面没有机会释放了。
而在main函数中,变量Task a离开作用域时,也通过析构函数释放handle,整个程序没有内存泄漏。


581

被折叠的 条评论
为什么被折叠?



