在 C++20 中,协程由多个部分组成,其中 等待体、协程体 和 promise_type 是关键组成部分。它们各自负责协程的不同方面,密切配合,共同完成协程的生命周期管理、执行与返回值的处理。
关系概述:
协程体(Coroutine Body)是我们定义的协程函数,即用户定义的异步操作逻辑,通常包含 co_await、co_yield 和 co_return 等操作。
等待体(Awaitable)是协程体中通过 co_await 关键字进行挂起的对象。它定义了协程如何挂起并等待某些异步操作的完成。
promise_type 是协程的内部管理对象,负责协程的生命周期,包括挂起、恢复、返回值以及异常的处理。每个协程都会有一个与之关联的 promise_type,它控制协程体的行为和返回值的处理。
它们的具体关系:
- 协程体 会依赖
promise_type 来控制协程的执行和返回值。
promise_type 负责管理协程的状态,包括决定何时挂起和恢复协程,以及处理协程的返回值。
- 等待体 是协程体中通过
co_await 进行挂起的对象,它决定协程何时挂起,并提供恢复协程的机制。
完整示例
为了更清晰地理解这些组件之间的关系,下面我将通过一个完整的实例展示它们是如何协同工作的。
代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103
| #include <iostream> #include <coroutine> #include <thread> #include <chrono>
struct MyAwaitable { bool ready = false;
bool await_ready() const noexcept { return ready; }
void await_suspend(std::coroutine_handle<> h) noexcept { std::cout << "Suspending coroutine..." << std::endl; std::thread([h]() { std::this_thread::sleep_for(std::chrono::seconds(2)); std::cout << "Resuming coroutine!" << std::endl; h.resume(); }).detach(); }
void await_resume() const noexcept { std::cout << "Resuming coroutine and returning result!" << std::endl; } };
struct MyCoroutine { struct promise_type; using handle_type = std::coroutine_handle<promise_type>;
handle_type h;
MyCoroutine(handle_type h) : h(h) {} ~MyCoroutine() { if (h) { h.destroy(); } }
void resume() { if (h) h.resume(); }
bool done() const { return h.done(); } };
struct MyCoroutine::promise_type { MyCoroutine get_return_object() { return MyCoroutine{std::coroutine_handle<promise_type>::from_promise(*this)}; }
std::suspend_always initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
void return_value(int value) { std::cout << "Returning value: " << value << std::endl; }
void unhandled_exception() { std::cout << "Unhandled exception in coroutine!" << std::endl; } };
MyCoroutine example_coroutine() { std::cout << "Before co_await" << std::endl;
MyAwaitable awaitable; co_await awaitable;
std::cout << "After co_await" << std::endl; co_return 42; }
int main() { std::cout << "Coroutine started!" << std::endl; auto coroutine = example_coroutine();
while (!coroutine.done()) { std::this_thread::sleep_for(std::chrono::milliseconds(100)); }
std::cout << "Coroutine finished!" << std::endl; return 0; }
|
代码解析:
1. MyAwaitable(等待体)
- 这是一个
awaitable 类型,表示一个可以被 co_await 等待的对象。它模拟了一个异步任务,通过 await_suspend() 来延迟协程的执行。
await_ready():检查是否可以立刻继续执行协程,如果返回 false,则协程会挂起。
await_suspend():协程挂起的地方,这里模拟了一个耗时 2 秒的异步操作(通过 std::thread)。
await_resume():协程恢复后,执行此函数。
2. promise_type
- 每个协程都会有一个与之关联的
promise_type,它负责管理协程的生命周期,包括挂起和恢复协程的时机。
get_return_object():创建并返回协程的最终对象(MyCoroutine)。
initial_suspend():协程开始时挂起,直到满足恢复条件(通常是异步任务完成)。
final_suspend():协程结束时挂起,允许执行一些清理工作。
return_value():协程的返回值处理函数,这里输出返回值 42。
3. MyCoroutine(协程体)
- 这是我们定义的协程体,它包含协程的逻辑。通过
co_await 挂起协程并等待 MyAwaitable 对象的完成。
- 协程体通过
promise_type 来获取协程句柄,句柄管理协程的状态(执行、恢复、销毁)。
4. example_coroutine()(协程函数)
- 协程函数是用户定义的异步操作。它通过
co_await 挂起协程,等待 MyAwaitable 对象的完成。
- 协程完成后,使用
co_return 返回一个值(42)。
5. main()
- 在主线程中启动协程,等待协程完成。主线程通过
while 循环检查协程是否完成,直到协程执行结束。
程序输出:
1 2 3 4 5 6 7 8
| Coroutine started! Before co_await Suspending coroutine... Resuming coroutine! Resuming coroutine and returning result! After co_await Returning value: 42 Coroutine finished!
|
关键点总结:
- 协程体(
example_coroutine) 包含了实际的异步任务,它通过 co_await 等待 awaitable 对象的完成。
promise_type 管理协程的生命周期,包括初始化、挂起、恢复和结束时的操作。
- 等待体(
MyAwaitable) 是协程体中通过 co_await 等待的对象,它负责挂起协程,直到某个异步事件发生(在本例中是模拟的 2 秒延迟)。
但是实际上,对于异步操作,我们不是必须要实现自定义的awaitable结构体,c++为future等标准库接口都实现了内置的awaitable组件,可以直接用于co_await
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
| #include <iostream> #include <coroutine> #include <future> #include <chrono>
std::future<int> async_task() { return std::async(std::launch::async, []() { std::this_thread::sleep_for(std::chrono::seconds(2)); return 42; }); }
struct my_coroutine { struct promise_type; // 协程的 promise_type using handle_type = std::coroutine_handle<promise_type>;
handle_type h;
my_coroutine(handle_type h) : h(h) {} ~my_coroutine() { if (h) h.destroy(); }
void resume() { if (h) h.resume(); } bool done() const { return h.done(); } };
struct my_coroutine::promise_type { my_coroutine get_return_object() { return my_coroutine{std::coroutine_handle<promise_type>::from_promise(*this)}; }
std::suspend_always initial_suspend() { return {}; } std::suspend_always final_suspend() noexcept { return {}; }
void return_value(int value) { std::cout << "Returning value: " << value << std::endl; }
void unhandled_exception() { std::cout << "Unhandled exception!" << std::endl; } };
my_coroutine example_coroutine() { std::cout << "Before co_await" << std::endl; auto result = co_await async_task(); // co_await 未来的结果 std::cout << "After co_await, result = " << result << std::endl; co_return result; }
int main() { std::cout << "Coroutine started!" << std::endl; auto coro = example_coroutine(); // 启动协程 while (!coro.done()) { std::this_thread::sleep_for(std::chrono::milliseconds(100)); } std::cout << "Coroutine finished!" << std::endl; return 0; }
|
C++20 中的协程通过语言特性对异步编程进行了极大的简化。它们的基本组成包括了 协程体(coroutine body)、promise 类型(promise_type)、协程句柄(coroutine handle) 和 协程的挂起与恢复机制。每个协程的实现都由几个关键组件组成,下面我将详细描述 C++20 协程的主要组成部分。
C++20 协程的主要组成部分
协程体(Coroutine Body)
- 协程体是用户编写的函数,它定义了协程的具体逻辑。
- 协程体内可以使用
co_await、co_yield 和 co_return,这些关键字用于挂起、恢复和返回协程的结果。
Promise 类型(Promise Type)
- 每个协程都有一个与之关联的
promise_type,它管理协程的状态和返回值。
promise_type 负责协程的执行和控制流程的挂起与恢复。
- 它会暴露一些特殊的成员函数来处理协程的生命周期,包括初始化、最终挂起、返回值、异常处理等。
协程句柄(Coroutine Handle)
- 协程句柄
std::coroutine_handle 是协程的核心,它允许我们在协程被挂起时保存协程的状态,并在适当的时候恢复协程。
- 协程句柄的功能包括:恢复协程、销毁协程等。
- 协程句柄通过
std::coroutine_handle<promise_type> 实现,它用于访问协程的 promise_type。
协程挂起与恢复机制
co_await:用于等待一个可等待对象(awaitable),并在该对象准备好时恢复协程。
co_yield:用于生成一个值并挂起当前协程,这在生成器(如异步流、迭代器)中特别有用。
co_return:用于返回协程的结果并结束协程的执行。
协程的返回值与结果处理
- 协程的返回值由
promise_type 的 return_value 方法来定义。返回值会通过 co_return 被传递给 promise_type,从而影响协程的最终结果。
- 协程的结果会通过协程句柄的
promise_type 被访问和处理。
协程的生命周期管理
- 协程的生命周期由编译器和标准库自动管理,程序员不需要显式地管理栈和上下文切换。通过
co_await 和 co_return,协程的生命周期会被适当挂起和恢复。
- 协程的销毁(在协程结束时)由协程句柄自动处理。
协程的异常处理
- 协程的异常处理通常通过
promise_type 的 unhandled_exception 函数来处理。在协程执行过程中,如果发生了异常,unhandled_exception 方法会被调用,并可以决定是否继续执行或者退出。
co_return 也可以处理协程返回时的异常情况。
协程的结构与执行流程
C++20 协程的执行流程可以分为以下几个步骤:
协程启动:
- 当协程被调用时,它首先会执行
promise_type 中定义的 initial_suspend 函数,通常这个函数会返回一个 std::suspend_always 或者 std::suspend_never 对象,决定协程是否立刻挂起。
协程挂起:
- 在协程体内,遇到
co_await 时,协程会挂起,并等待可等待对象的结果。
co_await 关键字会在底层调用 awaitable 类型的 await_suspend 方法,通常这个方法会将当前协程挂起,并等待某些异步事件(如网络响应或文件读取)完成。
协程恢复:
- 当协程挂起的条件(如异步操作完成)满足时,
awaitable 类型的 await_resume 方法会被调用,从而恢复协程的执行。
- 协程恢复后,控制流继续从挂起的地方执行。
协程结束:
- 当协程体执行到
co_return 或者末尾时,协程将会结束,并通过 promise_type 的 return_value 返回最终的值。
- 最后,协程会调用
final_suspend 函数,这个函数通常会决定协程在结束时是否挂起,或者协程结束时是否执行一些清理操作。
协程各部分详解
1. 协程体(Coroutine Body)
协程体是用户定义的函数,里面的 co_await、co_yield 和 co_return 会使得协程在执行过程中可以挂起、恢复和返回值。一个基本的协程体的代码可能如下所示:
1 2 3 4 5
| std::coroutine_handle<> example_coroutine() { co_await some_async_task(); std::cout << "Resumed coroutine" << std::endl; co_return; }
|
2. Promise 类型(Promise Type)
协程的 promise_type 是一个关键组件,它定义了协程的生命周期,包括如何挂起、如何返回结果和如何处理异常。一个 promise_type 可能包括以下方法:
initial_suspend():在协程开始时执行,决定协程是否立刻挂起。
final_suspend():在协程结束时执行,决定协程是否挂起。
return_value():当协程返回值时调用。
unhandled_exception():处理协程中的异常。
1 2 3 4 5 6 7 8
| struct MyPromise { int value;
std::suspend_always initial_suspend() { return {}; } std::suspend_always final_suspend() noexcept { return {}; } void return_value(int v) { value = v; } void unhandled_exception() { std::exit(1); } };
|
3. 协程句柄(Coroutine Handle)
协程句柄是一个表示协程执行状态的对象,它允许我们恢复协程,检查协程的状态,甚至销毁协程。通过 std::coroutine_handle<promise_type>,我们可以访问协程的 promise_type,并通过句柄来恢复协程。
1 2
| std::coroutine_handle<> handle = example_coroutine(); handle.resume();
|
4. 协程挂起与恢复机制
- **
co_await**:等待一个 awaitable 对象,挂起协程直到对象准备好。
- **
co_yield**:生成一个值并挂起协程,适用于生成器类型的协程。
- **
co_return**:返回一个值并结束协程。
1 2 3
| co_await some_async_task(); co_yield some_value; co_return some_result;
|
5. 协程的生命周期管理
C++20 协程的生命周期管理通常由编译器和标准库自动处理,包括协程的挂起、恢复、返回值和销毁。我们通过 co_await 和 co_return 来触发协程的状态转变。
6. 异常处理
C++20 协程支持在协程中处理异常。当协程抛出异常时,可以在 promise_type 中通过 unhandled_exception() 方法进行处理。
1 2 3
| void unhandled_exception() { }
|
总结
C++20 协程的实现涉及多个核心组件,每个部分协同工作,使得异步编程变得更加直观和高效。协程体、promise_type、协程句柄以及协程的挂起与恢复机制共同组成了 C++20 协程的基础结构。理解这些组成部分并掌握其使用,可以帮助你高效地编写异步代码。
是的,**co_await** 等待的 awaitable 对象通常会在任务完成时恢复(resume)原协程。关于 **co_yield**,它的作用和行为稍有不同,尤其是在生成器和协程之间的交互方面。
我们逐个详细讲解:
1. co_await 和 awaitable 对象
co_await 的作用是挂起协程,直到 awaitable 对象的操作完成。**awaitable** 是一种描述异步任务的对象,它通常会提供一个 await_suspend() 方法,当任务完成时,await_suspend() 会调用协程句柄(coroutine_handle)的 resume() 方法,恢复原协程的执行。
总结: co_await 等待的 awaitable 对象的任务完成时,一定会 让原协程恢复执行,除非发生了异常或其他错误,协程的状态是无法继续的。
示例:co_await 恢复协程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| #include <iostream> #include <coroutine> #include <thread> #include <chrono>
struct MyAsyncTask { bool ready = false;
bool await_ready() const noexcept { return ready; }
void await_suspend(std::coroutine_handle<> h) noexcept { std::cout << "Suspending coroutine..." << std::endl; std::thread([h]() { std::this_thread::sleep_for(std::chrono::seconds(2)); std::cout << "Resuming coroutine!" << std::endl; h.resume(); }).detach(); }
void await_resume() const noexcept { std::cout << "Async task completed, resuming coroutine!" << std::endl; } };
std::coroutine_handle<> my_coroutine() { std::cout << "Before co_await" << std::endl; MyAsyncTask task; co_await task; std::cout << "After co_await" << std::endl; }
int main() { std::cout << "Coroutine started!" << std::endl; auto handle = my_coroutine(); handle.resume(); std::this_thread::sleep_for(std::chrono::seconds(3)); std::cout << "Coroutine finished!" << std::endl; return 0; }
|
输出:
1 2 3 4 5 6 7
| Coroutine started! Before co_await Suspending coroutine... Resuming coroutine! Async task completed, resuming coroutine! After co_await Coroutine finished!
|
解释:
co_await task; 让协程挂起,直到 MyAsyncTask 的任务完成。
- 在
await_suspend() 中,我们模拟了一个异步操作(std::thread),并在操作完成后通过 h.resume() 恢复协程的执行。
- 结果是:协程在挂起 2 秒后恢复,继续执行
co_await 之后的代码。
2. co_yield 和返回值
co_yield 的作用是挂起协程,并将一个值返回给协程的调用者或迭代器。这个返回的值通常用于生成器模式中,让协程“生成”一个值,并在之后的执行中继续。
co_yield 不会完全结束协程,而是挂起协程并允许协程生成一个值,之后可以通过协程的恢复继续生成下一个值。
co_yield 的值:
- 当使用
co_yield 时,协程会挂起并将一个值返回给调用者。这个值通常是用户定义的类型,或者是协程函数返回的类型。
- 例如,在生成器中,
co_yield 生成一个序列中的下一个值,并且协程可以在继续生成下一个值时恢复。
示例:生成器模式(co_yield)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
| #include <iostream> #include <coroutine>
struct Generator { struct promise_type; using handle_type = std::coroutine_handle<promise_type>;
handle_type h;
Generator(handle_type h) : h(h) {} ~Generator() { if (h) h.destroy(); }
bool next() { h.resume(); return !h.done(); }
int current_value() { return h.promise().current_value; }
struct promise_type { int current_value = 0;
Generator get_return_object() { return Generator{handle_type::from_promise(*this)}; }
std::suspend_always initial_suspend() { return {}; }
std::suspend_always final_suspend() noexcept { return {}; }
void return_void() {}
void yield_value(int value) { current_value = value; }
void unhandled_exception() { std::exit(1); } }; };
Generator counter() { for (int i = 1; i <= 5; ++i) { co_yield i; } }
int main() { auto g = counter(); while (g.next()) { std::cout << "Generated value: " << g.current_value() << std::endl; } std::cout << "Finished!" << std::endl; return 0; }
|
输出:
1 2 3 4 5 6
| Generated value: 1 Generated value: 2 Generated value: 3 Generated value: 4 Generated value: 5 Finished!
|
解释:
co_yield i; 挂起协程并返回当前值 i,每次调用 g.next() 都会恢复协程,生成下一个值。
- 每次
co_yield 都将一个整数返回给调用者(主程序),并且协程保持挂起,等待下次恢复。
3. 挂起后如何恢复协程:
- 在
co_yield 中,协程的挂起和恢复是由协程句柄(std::coroutine_handle)控制的。当协程执行到 co_yield 时,它会返回一个值并挂起。
- 协程的恢复是通过
next() 或类似的机制来实现的。每次恢复时,协程会从挂起点继续执行,直到完成或再次遇到 co_yield 或 co_return。
- 对于生成器,恢复通常是通过调用
next() 方法来驱动协程的继续执行。在生成器的情况下,next() 会在每次 co_yield 后恢复协程。
4. co_return 的行为:
co_return 用于从协程中返回一个最终值,并结束协程的执行。与 co_yield 不同,co_return 会完全结束协程的生命周期,不能再恢复。
示例:co_return
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
| #include <iostream> #include <coroutine>
struct my_coroutine { struct promise_type; using handle_type = std::coroutine_handle<promise_type>;
handle_type h;
my_coroutine(handle_type h) : h(h) {} ~my_coroutine() { if (h) h.destroy(); }
void resume() { if (h) h.resume(); } bool done() const { return h.done(); } };
struct my_coroutine::promise_type { int return_value;
my_coroutine get_return_object() { return my_coroutine{std::coroutine_handle<promise_type>::from_promise(*this)}; }
std::suspend_always initial_suspend() { return {}; } std::suspend_always final_suspend() noexcept { return {}; }
void return_value(int value) { return_value = value; }
void unhandled_exception() { std::exit(1); } };
my_coroutine example_coroutine() { std::cout << "Before co_return" << std::endl; co_return 42; std::cout << "This line will never be printed!" << std::endl; }
int main() { auto coro = example_coroutine(); coro.resume(); std::cout << "Coroutine finished!" << std::endl; return 0; }
|
输出:
1 2
| Before co_return Coroutine finished!
|
解释:
- 在
co_return 42; 中,协程立即返回 42,并结束协程的执行。任何在 co_return 后的代码都不会被执行。
总结:
- **
co_await**:通过 awaitable 对象挂起协程,awaitable 完成任务后,协程会自动恢复。
- **
co_yield**:挂起协程并返回一个值给调用者,协程可以继续生成多个值,直到