#include <iostream>
#include <coroutine>
class CoroTaskSub {
public:
//编译器在处理协程函数时是通过其返回类型【即协程接口类型】,确定协程的承诺类型和协程句柄类型
struct promise_type;
using CoroHdl = std::coroutine_handle<promise_type>;
//需要定义从协程句柄到接口对象的转换构造函数,否则协程函数会报错"无法从std::coroutine_handle<promise_type>转换到CoroTaskSub"
CoroTaskSub(auto h)
: hdl{ h } {
}
~CoroTaskSub() {
if (hdl)
hdl.destroy(); // 协程句柄底层指针指向动态分配的内存,这部分内存需要显示手动释放
}
private:
CoroHdl hdl; // native coroutine handle
public:
struct promise_type {
auto get_return_object() { return CoroHdl::from_promise(*this); }//此处是通过承诺对象创建指向承诺对象的句柄对象,协程函数首次暂停返回给"调用方"时,创建的这个句柄对象会隐式转换到协程接口对象
auto initial_suspend() { return std::suspend_always{}; }
void return_void() { }
void unhandled_exception() { std::terminate(); }
auto final_suspend() noexcept { return std::suspend_always{}; }
CoroHdl subHdl = nullptr;
};
//使得协程接口类型可做等待器
bool await_ready() { return false; } // do not skip suspension
#if 0
auto await_suspend(auto awaitHdl) {
awaitHdl.promise().subHdl = hdl; // store sub-coroutine
// 通过返回内部协程的句柄,执行代理直接提供到内部协程上,内部协程恢复执行;当内部协程在下一个暂停点挂起;执行代理被定位到此接口函数被返回后的流程
//即不会重新在外部协程上重头执行co_await coro();因为执行代理的定位点不在那里,执行代理被定位到此函数返回之后的流程处;后面的流程就是执行代理被提供给main中【此函数不会被重复执行】
//相当于内部协程程或者其他协程在此回调返回的间隙处被提供执行代理恢复执行
return hdl;
}
#else
void await_suspend(auto awaitHdl) {//awaitHdl传递的是当前协程的句柄,因为co_await coro();语句在外边协程中,所以此时awaitHdl为外部协程的局部
// *this对象为内部协程函数的返回类型,所以其hdl成员存储的是内部协程的句柄;即将内部协程的句柄存储在外部协程的承诺对象中
//这需要外部协程的承诺对象的存储内部协程句柄的成员的类型与内部协程句柄的类型一致;外部协程函数与内部协程函数同样的协程接口类型(返回类型)刚好可以满足这一点
//这样就利用句柄的底层指针建立了一个协程状态链表结构
awaitHdl.promise().subHdl = hdl;
}
#endif
void await_resume() { }
//在main中调用,执行代理有时候给内部协程,有时候给外部协程=>类似于分发控制流
//这也侧面说明协程的执行绝不是传统的调用模型那样必须由调用方调用执行,执行流返回也需要返回给对应的调用方;
// 对于协程来说执行只是通过一个执行代理,而此执行代理由谁提供无所谓,二次执行时使用的执行代理是否是同一个也无所谓;
//协程严格来说没有所谓"调用方"的概念,协程只有执行代理的概念;
//使用协程时要摒弃调用堆栈的变化概念,取而代之的是执行代理的提供-转移-归还-保持-变换的概念
/*
* 执行代理的提供:协程句柄的resume接口提供
* 执行代理的转移:在等待器的await_suspend接口控制【返回某个其他协程的协程句柄时】
* 执行代理的归还:在等待器的await_suspend接口控制(归还执行代理给当初提供的"调用方")
* 执行代理的保持:在等待器的await_suspend接口控制(恢复当前暂停的协程)
* 执行代理的变换:在多次恢复协程执行的前后提供给该协程的执行代理并不一致【具体如何有效使用管理这种模式只有在深入应用协程后才能得出经验结论】
*/
//所以这里外部协程与内部协程间仅仅是根据协程句柄-承诺对象结构建立一个链表关系【只是提供一个协程句柄的寻址数据结构,与用全局容器保存不同协程的句柄无本质区别(等价)】;除此外内外协程间再无其他特别关系,执行代理可以提供给外协程,内协程,main,甚至其他协程;
//所以说协程是独立的,没有从属关系
/*
* 进程:有父子(从属)关系
* 线程:没有父子关系,是分线程概念
* 协程:没有父子关系,独立概念;没有调用关系;只要执行代理的流转【执行代理的提供-转移-归还-保持】
*/
bool resume() const {
if (!hdl || hdl.done())
return false; // nothing (more) to process
// find deepest sub-coroutine not done yet:
CoroHdl innerHdl = hdl;
while (innerHdl.promise().subHdl && !innerHdl.promise().subHdl.done())
innerHdl = innerHdl.promise().subHdl;
//第一次,因为句柄关联还未建立=>取到外部协程句柄,启动外部协程
// 协程句柄建立关联后,第二次会取到内部协程的句柄,调用其resume首次启动内部协程
//第三次会取到内部协程的句柄,调用其resume恢复内部协程
//第四次会取到外部协程的句柄,因为内部协程已经在最终暂停点暂停,调用其resume恢复外部协程
//第五次 按C++标准来说也会取到外部协程的句柄,因为内部协程已经在最终暂停点暂停,调用其resume恢复外部协程【但是由于msvc实现的缺陷,在noexcept声明的函数中引发异常导致程序终止】
innerHdl.resume();
return !hdl.done();
}
};
CoroTaskSub coro()
{
std::cout << " coro(): PART1\n";
co_await std::suspend_always{}; // SUSPEND
std::cout << " coro(): PART2\n";
}
CoroTaskSub callCoro()
{
std::cout << " callCoro(): CALL coro()\n";
//内部协程函数的调用会创建内部协程的状态对象以及接口对象,内部协程的接口对象也用作外部协程在此处的等待器对象
//回调函数await_suspend的调用会把内部协程的句柄存储在当前协程句柄指向的承诺对象中;这就建立了关联
co_await coro(); // call sub-coroutine
std::cout << " callCoro(): coro() done\n";
co_await std::suspend_always{}; // SUSPEND
std::cout << " callCoro(): END\n";
}
int main()
{
//创建外部协程
auto coroTask = callCoro();
std::cout << "MAIN: callCoro() initialized\n";
//这里coroTask.resume()首次被调用时,此时内部协程的句柄还未挂载到外部协程的承诺对象中;所以首次resume只恢复外部协程
while (coroTask.resume()) //
std::cout << "MAIN: callCoro() suspended\n";
std::cout << "MAIN: callCoro() done\n";
return 0;
}