​普通函数​​一次调用一次返回.

​协程​​多次调用多次返回,且​​协程​​有状态,返回值不一样.组织​​自己​​的任务调试器,类似​​软中断​​.

这里有粗略参考

​C++20协程​​特点

​对象​

作用

​协程句柄​

管理协程周期

​承诺对象​

配置(​​启动挂起,执行结束前挂起等​​)行为,传递返回值

​协待+可等待对象​

定义挂起点,交换数据.

我们重点理解​​协待/协中​​.

#包含<io流>
#包含<可恢复>

名字空间 ;

可恢复事情
{
承诺类型
{
可恢复事情 取中对象()
{
可恢复事情(协程句柄<承诺类型>::从承诺(*));
}
初始挂起(){ 从不挂起{};}
止挂起(){ 从不挂起{};}
中空(){}
};
协程句柄<承诺类型>_协程=空针;
可恢复事情()=默认;
可恢复事情(可恢复事情 &)=;
可恢复事情&符号=(可恢复事情 &)=;
可恢复事情(可恢复事情&&其他)
:_协程(其他._协程){
其他._协程=空针;
}
可恢复事情&符号=(可恢复事情&&其他){
(&其他!=){
_协程=其他._协程;
其他._协程=空针;
}
}
可恢复事情(协程句柄<承诺类型>协程):_协程(协程)
{
}
~可恢复事情()
{
(_协程){_协程.消灭();}
}
恢复(){_协程.恢复();}
};

可恢复事情 计数器(){//协程
输出<<"调用计数器";
( i=1;;i++)
{
协待 标::总是挂起{};
输出<<"恢复计数器"<<i<<")\n";
}
}

()
{
输出<<"主调用";
可恢复事情 计数器=计数器();
输出<<"主恢复";
计数器.恢复();
计数器.恢复();
计数器.恢复();
计数器.恢复();
计数器.恢复();
输出<<"完成";
0;
}

要定义​​C++20​​协程,要求​​返回类型​​有个​​承诺类型​​的子类型,

承诺类型

可恢复事情 计数器(){
__计数器环境*__环境= __计数器环境{};
__中=__环境->_承诺.取中对象();
协待 __环境->_承诺.初始挂起();
输出<<"调用计数器";
( i=1;;i++)
{
协待 标::总是挂起{};
输出<<"恢复计数器"<<i<<")\n";
}
__止挂起标签:
协待 __环境->_承诺.止挂起();
}

要理解​​承诺类型​​.

 可恢复事情
{
承诺类型
{
可恢复事情 取中对象();
初始挂起(){ 从不挂起{};}
止挂起(){ 从不挂起{};}
中空(){}
};

​计数器​​中已加入​​取中对象,初始挂起,止挂起​​等函数,​​__计数器环境​​是编译器生成的上下文,保存​​协程挂起还原​​的空间,由

__中=__环境->承诺.取中对象();

创建​​返回​​对象.在执行​​协程​​前,先用

协待 __环境->_承诺.初始挂起();

来判断是否挂起,上面返回​​从不挂起​​,如果返回​​总是挂起​​,则挂起​​该协程​​.结束​​协程​​前调用​​止挂起​​,来判断结束前是否​​挂起​​.

同样,对​​协中​​,调用​​承诺​​的​​返回空/返回值​​,最后跳至​​协程尾​​:

__环境->_承诺->中空(); 止挂起标签;

对​​协产​​,类似​​协中​​,如​​协产 "你好"​​,翻译成:

协待 __环境->_承诺->产生值("你好");

可见​​协产​​,就是​​协待​​的​​语法糖​​,调用​​承诺​​的​​产生值​​方法.如无该方法,则报错.从而​​承诺​​起着​​内部控制协程,并传递(异常和结果)至外部系统​​的作用.

协程句柄

用来​​外部​​控制​​协程生命期​​.

 可恢复事情
{
协程句柄<承诺类型>_协程=空针;
~可恢复事情()
{
(_协程){_协程.消灭();}
}
恢复(){_协程.恢复();}
};

下面为​​协程句柄​​实现:

<> 协程句柄<>{
常式 协程句柄()无异;
常式 协程句柄(空针型)无异;
协程句柄&符号=(空针型)无异;
常式 *地址() 无异;
常式 协程句柄 从地址(*地址);
常式 符号 () 无异;
完成();
符号()();
恢复();
消灭();
:
*;
};

每个​​承诺类型​​,派生相应​​协程句柄<型>​​的特化版.如​​协程句柄<可恢复事情::承诺类型>​​:

<型名 承诺>
协程句柄:协程句柄<>
{
承诺&承诺() 无异;
协程句柄 从承诺(承诺&)无异;
};

​协程句柄​​用于控制​​协程​​的​​生命期​​.如​​恢复/消灭/完成/操作符()()(用于初次执行)​​.注意,不要再次释放​​协程​​.

可恢复事情 取中对象()
{
可恢复事情(协程句柄<承诺类型>::从承诺(*));
}

可通过该​​协程句柄​​控制​​协程生命期​​.

​协程​​通过​​协待​​与​​可等待对象​​来挂起​​协程​​与​​同外界交换数据​​.

协待 可等待;
//扩展为.
(!可等待.准备好协()){//未准备好
//挂起点
可等待.挂起协(协程句柄);
//调用恢复点
}
可等待.恢复协();

​可等待​​主要由3个函数组成.​​准备好协​​,为​​真​​则不必等待,为​​假​​则​​要​​等待.不等待则​​恢复​​,否则​​挂起​​.​​挂起协​​一般​​记录/设置​​状态,而​​恢复协​​可操作​​外部返回值​​.

这样,使用者可定制​​挂起和恢复​​.通过实现不同的​​可等待​​来​​协程异步操作​​.

还有其他​​可等待​​对象:​​承诺类型::转换协/符号 协待()/可等待​​.

​协程​​可实现​​单子(等待-挂起)/任务(等待)/生成器(挂起)/异步生成器(等待+产生)​​.

C++20的调度器

​管理任务/可等待机制(挂起点,交换数据)/回调机制(反馈)​​,核心对象​​调度任务​​:

 协中函数=标::函数<( 协中对象*)>;

i预定任务
{//封装协程对象.
调度器;
:
i预定任务()=;
i预定任务( 预定任务c++17&)=;
i预定任务(正64型 任务标识,调度器*管理者);
虚~i预定任务();
正64型 取标识();
()=0;
是完成()=0;
协任务状态 取协状态()=0;
绑定休息句柄(正64型 句柄);
等待模式 取等待模式();
取等待超时();
<型名 等待事件类型>
绑定恢复对象(等待事件类型&&等待事件)->标::允许如型<标::是的基<恢复对象,等待事件类型>::>;
<型名 等待事件类型>
按类型取恢复对象()->标::允许如型<标::是的基<恢复对象,等待事件类型>::,等待事件类型*>;
有恢复对象() 无异;
清理恢复对象();
是上个调用下一() 无异;
是上个调用超时() 无异;
是上个调用失败() 无异;
加子任务(正64型 线标);
加等待通知任务(正64型 线标);
&取子任务数组();
&取等待通知数组();
终止();
调度器*取管();
i预定任务*当前任务();
干产生(等待模式 模式, 等待时间毫秒=0);
置中函数(协中函数&&函数);
确实中( 协中对象&对象);
确实中();
保护:
正64型 m任务标识;
调度器* m管理者;
标::向量<正64型> m子数组;
标::向量<正64型> m等待通知数组;
//用来从协程返回的值.
等待模式 m等待模式=等待模式::等待闲着;
m等待超时=0;
//用来发送至协程值,现在为异步事件.
反射::用户对象 m恢复对象;
//异步等待,当成功执行`异步等待`时,向协程传递值
正64型 m休息句柄=0;
m是终止=;
协中函数 m协中函数;
};

预定任务c++20: i预定任务
{
:
预定任务c++20(正64型 任务标识,协任务函数&&任务函数,调度器*管理者);
~预定任务c++20();
();
是完成() ;
协任务状态 取协状态() ;
绑定本到协任务();
协恢复任务c++20&取恢复任务();
保护:
协恢复任务c++20 m协恢复任务;
//内部有`承诺类型`实现,通过它访问协程.
协任务函数 m任务函数;
//存储函数对象.
};

我们保存了​​协程对象/相关函数对象​​,因为如果​​协程​​如果为​​λ​​,​​编译器​​不会维护​​λ​​生命期及​​λ​​捕捉的函数.

处理​​产生​​.

 调度器::更新()
{
r工作室分析器方法信息(s更新,"调度器::更新()",r工作室::分析器组类型::k逻辑工作);
r工作室分析器自动区域(s更新);

//先要干掉任务
(!m需要干掉数组.空的())
{
线标=m需要干掉数组.();
m需要干掉数组.();
*临任务=按标识取任务(线标);
(临任务!=空针)
{
消灭任务(临任务);
}
}

//因为现在不执行下帧任务,而保留临时队列.
推导(m开始任务帧)临帧任务;
m开始任务帧.交换(临帧任务);

(!临帧任务.空的())
{
任务标识=临帧任务.();
临帧任务.();
*任务=按标识取任务(任务标识);
日志检查错误(任务);
(任务)
{
加到立即跑(任务);
}
}
}

调度器::加到立即跑(i预定任务*预定任务)
{
日志进程错误(预定任务);
预定任务->();

(预定任务->是完成())
{
消灭任务(预定任务);
;
}

{
等待模式=预定任务->取等待模式();
等待超时毫秒=预定任务->取等待超时();
r工作室::逻辑::等待模式;
开关(预定任务->取等待模式())
{
从不等待:
加到立即跑(预定任务);
;
等待下一帧:
加到下一桢跑(预定任务);
;
等待无超时通知:
用超时等待通知:
{
处理等待通知任务(预定任务,等待模式,等待超时毫秒);
}
;
等待闲着:
;
默认:
r工作室错误(不能跑在此错误());
;
}
}
退出0:
;
}

在​​任务->跑​​后,到达下个​​挂起点​​,外部代码根据​​挂起模式​​控制行为,主要有:​​从不等待,等待下一帧,等待无超时通知(外界通知后),用超时等待通知(通知或超时后),等待闲着(特殊,删任务)​​模式.

然后是恢复处理.

​恢复​​通过向​​关联任务​​传递​​恢复对象​​来实现.

//不是真实事件通知,
<型名 E>
按等待对象恢复任务(E&&等待对象)->标::允许如型<标::是的基<恢复对象,E>::>
{
线标=等待对象.任务标识;
(是任务在等待集(线标))
{
//仅可等待集中任务可恢复.
*任务=按标识取任务(线标);
(r工作室可能(任务!=空针))
{
任务->绑定恢复对象(标::前向<E>(等待对象));
加到立即跑(任务);
}

任务等待通知完成时(线标);
}
}

再用

#定义 r协取恢复对象(恢复对象类型) r协本任务()->按类型取恢复对象<恢复对象类型>()

宏来取​​恢复对象​​.传递​​恢复对象​​后,加​​协程​​至​​m读任务​​队列中,以便​​更新​​中唤醒它.下面为如何实现​​可等待​​:

 r工作室应用服务接口 请求远调用
{
:
请求远调用()=;
//构造=删
~请求远调用()=默认;

请求远调用( 逻辑::游戏服务调用者针&代理, 标::串视 函数名,反射::实参&&, 超时毫秒):
m代理(代理)
,m函数名(函数名)
,m实参(标::前向<反射::实参>())
,m超时毫秒(超时毫秒)
{}
准备好协()
{
;
}
挂起协(协程句柄<>) 无异
{
*任务=r协本任务();
环境=标::造共<服务环境>();
环境->任务标=任务->取标识();
环境->超时=m超时毫秒;
实参=m实参;
m代理->干动态调用(m函数名,标::移动(实参),环境);
任务->干产生(等待模式::等待无超时通知);
}
::r工作室::逻辑::远调用恢复对象*恢复协() 无异
{
r协取恢复对象(逻辑::远调用恢复对象);
}
:
逻辑::游戏服务调用者针 m代理;
标::串 m函数名;
反射::实参 m实参;
m超时毫秒;
};

重点是​​准备好协/挂起协/恢复协​​的实现.有时需要​​完成协程​​时,发送通知或​​传递返回值​​或提供​​下步操作值​​.

任务->置中函数([,服务器,实体,命令头,路由器地址,请求头,环境]( 协中对象*对象){
*中对象=动转< 协远调用中对象*>(对象);
(r工作室可能(中对象))
{
干响应远调用(服务器,实体.(),路由器地址,&命令头,
请求头,常转<服务环境&>(环境),
中对象->远调用结果类型,中对象->总中,中对象->中值);
}
});

通过​​λ​​绑定函数,利用​​协中​​向​​承诺类型​​传递返回值.

协任务信息 心跳服务::干心跳(逻辑::调度器&调度器, 测试值)
{
调度器.创建任务20(
[测试值]()->逻辑::协恢复任务c++20{
协待 逻辑::协任务::休息(1000);
打印格式("完成任务");
协中 协远调用中对象(反射::值(测试值+1));
}
);
}

最后用​​返回值​​设置回调.

 协恢复任务c++20::承诺类型::返回值( 协中对象&对象)
{
*任务=r协本任务();
任务->确实中(对象);
}

​额外对象​​作为​​事件​​传递给​​业务​​.发起事件后,删除​​协程任务​​.示例如下:

 客户代理=m远调用客户->创建服务代理("多在线.心跳");
m调度器.创建任务20([客户代理]()->r工作室::逻辑::协恢复任务c++20{
*任务=r协本任务();
打印格式("第1步,任务是%llu",任务->取标识());
协待 r工作室::逻辑::协任务::下一帧{};
打印格式("产生后,第2步");
c=0;
(c<5)
{
打印格式("当循环中%d",c);
协待 r工作室::逻辑::协任务::休息(1000);
c++;
}

(c=0;c<5;c++)
{
打印格式("对循环中%d",c);
协待 r工作室::逻辑::协任务::下一帧{};
}
打印格式("第3步,%d",c);
新任务标识=协待 r工作室::逻辑::协任务::创建任务(,[]()->逻辑::协恢复任务c++20{
打印格式("子协程");
协待 r工作室::逻辑::协任务::休息(2000);
打印格式("休息后");
});
打印格式("协程中新任务%d",新任务标识);
打印格式("开始等待任务");
协待 r工作室::逻辑::协任务::等待完成任务{新任务标识,10000};
打印格式("等待后");

r工作室::逻辑::协任务::请求远调用 远调用请求{客户代理,"干心跳",r工作室::反射::实参{3},5000};
*远调用中=协待 远调用请求;
(远调用中->远调用结果类型==r工作室::网络::远调用响应结果类型::请求下一)
{
断定(远调用中->总中==1);
返回值=远调用中->中值.<>();
断定(返回值==4);
打印格式("成功,值为%d",返回值);
}

{
打印格式("失败,值为%d",()远调用中->远调用结果类型);
}

协待 r工作室::逻辑::协任务::休息(5000);
打印格式("休息5秒后,第4步");
协中 r工作室::逻辑::协无效;
});
/*
step1: task is 1
step2 after yield!
in while loop c=0
in while loop c=1
in while loop c=2
in while loop c=3
in while loop c=4
in for loop c=0
in for loop c=1
in for loop c=2
in for loop c=3
in for loop c=4
step3 5
new task create in coroutine: 2
Begin wait for task!
from child coroutine!
after child coroutine sleep
After wait for task!
service yield call finish!
rpc coroutine run suc, val = 4!
step4, after 5s sleep
*/

​对比C++17​​优点:

​序号​

优点

​1​

代码更精简.

​2​

编译器可​​自动​​处理栈变量.

​3​

​协待​​可直接返回值,且有​​强制​​类型约束.

​4​

协程就是​​λ​​,可充分利用​​λ​​.