1. 什么是协程
在cppreference中对协程做了如下定义如果一个函数拥有下面的特征,那么他就算是一个协程
- 使用
co_await运算符挂起当前执行过程 - 使用
co_yield运算符挂起当前执行过程并返回 - 使用
co_return运算发完成当前执行过程并返回 - 协程不能有可变参数
- 协程不能使用
return关键字返回,不能使用占位符返回类型包括auto或concept constexpr函数,构造析构函数不能是协程
2. 最简单的协程和它的’返回‘类型
常规函数通过返回一些结果包括返回空来向调用者返回。相比之下,协程不能使用return关键字。但是即使不使用return关键字协程的ReturnObject中也同样有一些设定好的返回对象,在ReturnObject对象中嵌套了promise_type,promise_type是一组特定的函数集合。
// 举个例子
#include <coroutine> // -fcoroutines
int foo() { co_return 2; } // unable to find the promise type for this coroutine
int bar() { co_await 2; } // unable to find the promise type for this coroutine
int foo2() { return 2; } // ok
int bar2() { return 2; } // ok
我举例了两个编译错误的协程函数和两个正常的普通函数
其中协程函数使用了co_return和co_await。co_return和return关键字非常类似,co_return能被用来返回到调用者的上下文中,co_await则会返回到其他的协程上下文或者其调用者上下文中。有一点需要注意的是co_await是一个运算符,并需要一个有效的操作数,看起来像是co_await expr。
可以看到编译错误提示unable to find the promise type for this coroutine,说明编译器无法找到promise type这个类型,在这种情况下,需要正确设置正确的promise type类型
根据以上信息,在cppreference中要求协程返回的对象必须嵌套promise_type,根据这个类名与实现方式编译器生成代码,该代码调用promise_type中的函数实现。
举个例子
struct ReturnObject {
struct promise_type { // 嵌套类型
promise_type() = default; // 默认构造函数
ReturnObject get_return_object() { return {}; } // 在嵌套类型中返回外部类型
Awaitable initial_suspend() { return {}; } // 在协程创建时调用,并返回 Awaitable 类型
Awaitable final_suspend() { return {}; } // 在协程结束时调用,并返回 Awaitable 类型
void unhandled_exception() {} // 用于处理一些意外异常
}
}
ReturnObject bar() { co_await expr; }
// !不能运行,因为Awaitable未知
// Awaitable = std::suspend_always/std::suspend_never
在例子中Awaitable对象,实际为std::suspend_always或者std::suspend_never其中之一
使用std::suspend_always可在co_await时挂起协程
使用std::suspend_never可在co_await时不挂起协程
举个例子
struct ReturnObject{
struct promise_type{
promise_type() = default;
ReturnObject get_return_object() { return {}; }
std::suspend_always initial_suspend() { return {}; }
std::suspend_never final_suspend(){ return {}; }
void unhandled_exception(){}
};
};
ReturnObject foo(){
std::cout << "1. hello from coroutine";
co_await std::suspend_always{};
std::cout << "2. hello from coroutine";
}
int main(){foo(); }
这个例子什么都不会输出,因为main()函数先构建了ReturnObject这个对象,顺序如下
- 使用
promise_type构造函数 - 调用
get_return_object()并保存结果于局部变量中 - 调用
initial_suspend()并使用co_await运算此函数的返回值
所以例子卡在了调用initial_suspend()上,如果将initial_suspend()的返回值改为std::suspend_never,则可以顺利执行第一条语句,如果将foo()中改为co_await std::suspend_never{};则可以一直执行。
最后一个例子可以看出执行顺序
#include <coroutine>
#include <iostream>
//global counter
int num{0};
struct ReturnObject{
struct promise_type{
promise_type(){std::cout << num++ << ". promise_type default ctor\n";}
ReturnObject get_return_object() {
std::cout << num++ << ". promise_type::get_return_object\n";
return {};
}
std::suspend_never initial_suspend() {
std::cout << num++ << ". promise_type::initial_suspend\n";
return {};
}
std::suspend_never final_suspend(){
std::cout << num++ << ". promise_type::final_suspend\n";
return {};
}
void unhandled_exception(){std::cout << num++ << ".throw\n";}
};
};
ReturnObject foo(){
std::cout << num++ << ". hello from coroutine\n";
co_await std::suspend_never{}; //never suspend the coroutine at this point
//co_await std::suspend_always{}; //suspend the coroutine at this point
std::cout << num++ << ". hello from coroutine\n";
}
int main(){foo();}
/* output
0. promise_type default ctor
1. promise_type::get_return_object
2. promise_type::initial_suspend
3. hello from coroutine
4. hello from coroutine
5. promise_type::final_suspend
*/