在 C++20 中,协程由多个部分组成,其中 等待体协程体promise_type 是关键组成部分。它们各自负责协程的不同方面,密切配合,共同完成协程的生命周期管理、执行与返回值的处理。

关系概述:

  1. 协程体(Coroutine Body)是我们定义的协程函数,即用户定义的异步操作逻辑,通常包含 co_awaitco_yieldco_return 等操作。

  2. 等待体(Awaitable)是协程体中通过 co_await 关键字进行挂起的对象。它定义了协程如何挂起并等待某些异步操作的完成。

  3. 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>

// 定义一个简单的 awaitable 类型,模拟一个异步任务
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(); // 2秒后恢复协程
}).detach();
}

void await_resume() const noexcept {
// 恢复协程后执行的操作
std::cout << "Resuming coroutine and returning result!" << std::endl;
}
};

// 定义协程体
struct MyCoroutine {
struct promise_type; // 协程的 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(); // 恢复协程
}

// 通过协程句柄访问 promise_type
bool done() const { return h.done(); }
};

// 定义协程的 promise_type
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;

// 使用 awaitable 对象挂起协程
MyAwaitable awaitable;
co_await awaitable; // 挂起协程,直到 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 协程的主要组成部分

  1. 协程体(Coroutine Body)

    • 协程体是用户编写的函数,它定义了协程的具体逻辑。
    • 协程体内可以使用 co_awaitco_yieldco_return,这些关键字用于挂起、恢复和返回协程的结果。
  2. Promise 类型(Promise Type)

    • 每个协程都有一个与之关联的 promise_type,它管理协程的状态和返回值。
    • promise_type 负责协程的执行和控制流程的挂起与恢复。
    • 它会暴露一些特殊的成员函数来处理协程的生命周期,包括初始化、最终挂起、返回值、异常处理等。
  3. 协程句柄(Coroutine Handle)

    • 协程句柄 std::coroutine_handle 是协程的核心,它允许我们在协程被挂起时保存协程的状态,并在适当的时候恢复协程。
    • 协程句柄的功能包括:恢复协程、销毁协程等。
    • 协程句柄通过 std::coroutine_handle<promise_type> 实现,它用于访问协程的 promise_type
  4. 协程挂起与恢复机制

    • co_await:用于等待一个可等待对象(awaitable),并在该对象准备好时恢复协程。
    • co_yield:用于生成一个值并挂起当前协程,这在生成器(如异步流、迭代器)中特别有用。
    • co_return:用于返回协程的结果并结束协程的执行。
  5. 协程的返回值与结果处理

    • 协程的返回值由 promise_typereturn_value 方法来定义。返回值会通过 co_return 被传递给 promise_type,从而影响协程的最终结果。
    • 协程的结果会通过协程句柄的 promise_type 被访问和处理。
  6. 协程的生命周期管理

    • 协程的生命周期由编译器和标准库自动管理,程序员不需要显式地管理栈和上下文切换。通过 co_awaitco_return,协程的生命周期会被适当挂起和恢复。
    • 协程的销毁(在协程结束时)由协程句柄自动处理。
  7. 协程的异常处理

    • 协程的异常处理通常通过 promise_typeunhandled_exception 函数来处理。在协程执行过程中,如果发生了异常,unhandled_exception 方法会被调用,并可以决定是否继续执行或者退出。
    • co_return 也可以处理协程返回时的异常情况。

协程的结构与执行流程

C++20 协程的执行流程可以分为以下几个步骤:

  1. 协程启动:

    • 当协程被调用时,它首先会执行 promise_type 中定义的 initial_suspend 函数,通常这个函数会返回一个 std::suspend_always 或者 std::suspend_never 对象,决定协程是否立刻挂起。
  2. 协程挂起:

    • 在协程体内,遇到 co_await 时,协程会挂起,并等待可等待对象的结果。
    • co_await 关键字会在底层调用 awaitable 类型的 await_suspend 方法,通常这个方法会将当前协程挂起,并等待某些异步事件(如网络响应或文件读取)完成。
  3. 协程恢复:

    • 当协程挂起的条件(如异步操作完成)满足时,awaitable 类型的 await_resume 方法会被调用,从而恢复协程的执行。
    • 协程恢复后,控制流继续从挂起的地方执行。
  4. 协程结束:

    • 当协程体执行到 co_return 或者末尾时,协程将会结束,并通过 promise_typereturn_value 返回最终的值。
    • 最后,协程会调用 final_suspend 函数,这个函数通常会决定协程在结束时是否挂起,或者协程结束时是否执行一些清理操作。

协程各部分详解

1. 协程体(Coroutine Body)

协程体是用户定义的函数,里面的 co_awaitco_yieldco_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_awaitco_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_awaitawaitable 对象

  • 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>

// 自定义 awaitable 类型
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;
}
};

// 使用 co_await 的协程函数
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_yieldco_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**:挂起协程并返回一个值给调用者,协程可以继续生成多个值,直到