当前位置: 首页 > news >正文

深圳苍松大厦 网站建设适合小学生的最新新闻

深圳苍松大厦 网站建设,适合小学生的最新新闻,域名通过了才可以做网站吗,提交网站的入口地址一、c20的协程概念 在c20标准后,在一些函数中看到co_await、co_yield、co_return这些关键词,这是c20为协程实现设计的运算符。 协程是能暂停执行以在之后恢复的函数。原来我们调用一个功能函数时,只要调用了以后,就要完整执行完该…

一、c++20的协程概念

        在c++20标准后,在一些函数中看到co_await、co_yield、co_return这些关键词,这是c++20为协程实现设计的运算符。

        协程是能暂停执行以在之后恢复的函数。原来我们调用一个功能函数时,只要调用了以后,就要完整执行完该功能函数所有步骤(语句)才能回来执行自身的步骤,对于一些功能函数其由很长的执行周期,该执行周期中调用者就不能处理自身一些事务。在协程出现以前我们就需要回调、阻塞等手段综合设计实现。

        协程就是为了解决类似这种问题的。调用者调用协程函数后,执行到中途可以通过co_yield暂停挂起,返回自身执行事务,然后在通过resume唤醒恢复协程,协程函数会从挂起标识处继续往下执行。整个协程函数执行周期内,可以多次返回调用自身。在协程函数结束后,通过co_return还可以返回协程结果或协程内部对象。

        协程是无栈的:它们通过返回到调用方暂停执行,并且从栈分离存储恢复执行需要的数据。这样就可以编写异步执行的顺序代码(例如,不使用显式的回调来处理非阻塞 I/O),还支持对惰性计算的无限序列上的算法及其他用途。 

        例如用关键词 co_yield 暂停执行并返回一个值:

task coroutine_func(int n = 1) 
{int i = 0;while(i<n){co_yield i++;}
}

        或者用关键词 co_return 完成执行并返回一个值或void:

task coroutine_func(int n = 1) 
{co_return; 
}

二、c++20协程库

        这些关键词都做了啥事情呢。在c++20协程库中,提供了以下支持库来实现协程:

协程特征,定义于头文件 <coroutine>
coroutine_traits        (C++20)用于发现协程承诺类型的特征类型(类模板) 协程柄,定义于头文件 <coroutine>
coroutine_handle        (C++20)用于指代暂停或执行的协程(类模板) 无操作协程,定义于头文件 <coroutine>
noop_coroutine          (C++20)创建在等待或销毁时无可观察作用的协程柄(函数) 
noop_coroutine_promise  (C++20)用于无可观察作用的协程(类) 
noop_coroutine_handle   (C++20)std::coroutine_handle<std::noop_coroutine_promise> ,有意用于指代无操作协程
(typedef) 平凡可等待体,定义于头文件 <coroutine>
suspend_never           (C++20)指示 await 表达式应该决不暂停(类) 
suspend_always          (C++20)指示 await 表达式应该始终暂停(类) 

         std::coroutine_traits从协程的返回类型与形参类型确定承诺类型。

//定义于头文件 <coroutine> ,(C++20 起) 
template< class R, class... Args > struct coroutine_traits;/*模板形参
*R - 协程的返回类型
*Args - 协程的形参类型,若协程为非静态成员函数则包括隐式对象形参
*/

        标准库实现提供与 R::promise_type 相同的公开可访问成员类型 promise_type ,若该有限定标识合法并代表类型。否则它无成员。coroutine_traits 的程序定义特化应当定义公开可访问的成员类型 promise_type ,否则行为未定义。

//成员类型
//类型             定义
promise_type         R::promise_type     //若它合法,或由程序定义特化提供

         2.1 coroutine_handle句柄

        其中最主要的就是coroutine_handle句柄,类模板 coroutine_handle 能用于指代暂停或执行的协程,定义于头文件 <coroutine>:

//(C++20 起) 
/*结构体主模板,可从 Promise 类型的承诺对象创建。*/
template< class Promise = void > struct coroutine_handle;       /*特化std::coroutine_handle<void>擦除承诺类型。它可从其他特化转换*
template<> struct coroutine_handle<void>;  /*特化std::coroutine_handle<std::noop_coroutine_promise>指代无操作协程。不能从承诺对象创建它*/                     
template<> struct coroutine_handle<std::noop_coroutine_promise>;
using noop_coroutine_handle =std::coroutine_handle<std::noop_coroutine_promise>;

        std::coroutine_handle 的每个特化均为可平凡复制 (TriviallyCopyable) ,并保有一个指向协程状态的指针作为其仅有的非静态成员。添加 coroutine_handle 的特化的程序行为未定义。std::coroutine_handle 功能如下:

//(C++20) 
成员函数
(构造函数)     构造 coroutine_handle 对象(公开成员函数) 
operator=     赋值 coroutine_handle 对象(公开成员函数) 
from_promise    [静态]从协程的承诺对象创建 coroutine_handle(公开静态成员函数) 转换
operator coroutine_handle<> 获得擦除类型的 coroutine_handle(公开成员函数) 观察器
done             检查协程是否已完成(公开成员函数) 
operator bool    检查柄是否表示协程(公开成员函数) 控制
operator()
resume           恢复协程执行(公开成员函数) 
destroy          销毁协程(公开成员函数) 承诺访问
promise          访问协程的承诺对象(公开成员函数) 导出/导入
address         导出底层地址,即支撑协程的指针(公开成员函数) 
from_address    [静态]从指针导入协程(公开静态成员函数) 非成员函数
operator==      比较二个 coroutine_handle 对象(函数) 
operator<=>     比较二个 coroutine_handle 对象(函数) 辅助类
std::hash<std::coroutine_handle>  std::coroutine_handle 的散列支持(类模板特化) 

        std::coroutine_handle,协程句柄是从协程外部操纵的,这是用于恢复协程执行或销毁协程帧的非拥有柄;承诺(promise)对象,从协程内部操纵,协程通过此对象提交其结果或异常。

        2.2 std::coroutine_handle实现案例

        现在来看如何通过std::coroutine_handle实现协程函数的,下面定义一个简单的协程例子:

//test0.h
#ifndef _TEST_0_H_
#define _TEST_0_H_
void coroutine_first_test(void);
#endif //_TEST_0_H_
//test0.cpp
#include "test0.h"#include <coroutine>
#include <iostream>struct task {struct promise_type {task get_return_object() { std::cout << "task::promise_type.get_return_object \n";return task{Handle::from_promise(*this)}; }//返回std::suspend_never(这个随后说明) ,初始化后就继续运行std::suspend_never initial_suspend() { std::cout << "task::promise_type.initial_suspend \n";return {}; }std::suspend_never final_suspend() noexcept { std::cout << "task::promise_type.final_suspend \n";return {}; }std::suspend_always yield_value(const int &val) noexcept { //co_yield调用std::cout << "task::promise_type.yield_value " << val << "\n";return {};}void return_void() {}   //co_return调用void unhandled_exception() {std::cout << "task::promise_type.unhandled_exception \n";}};using Handle = std::coroutine_handle<promise_type>;//协程句柄explicit task(Handle coroutine) : m_coroutine{coroutine} {} //get_return_object时调用task() = default;~task() { std::cout << "~task \n";if (m_coroutine) //自行销毁{m_coroutine.destroy(); }}// task(const task&) = delete;// task& operator=(const task&) = delete;Handle m_coroutine; //协程句柄
};task coroutine_func(int n = 0) 
{int i = 0;while(i<n){co_yield i++;std::cout << "coroutine dosomthing" << i << "\n";}co_return; 
}void coroutine_first_test(void)
{auto c0_obj = coroutine_func(10);for (size_t i = 0; i < 5; i++){c0_obj.m_coroutine.resume();//唤醒协程std::cout << "caller dosomthing" << i << "\n";}
};
//main.cpp
#include "test0.h"int main(int argc, char* argv[])
{coroutine_first_test();return 0;
};

        task是个自定义的结构体,为了能作为协程的返回值,需要定义一个内部 promise_type结构体。

        【1】协程开始执行时,它进行下列操作:

  1. 用 operator new 分配协程状态对象(coroutine state)。
  2. 将所有函数形参复制到协程状态中:按值传递的形参被移动或复制,按引用传递的参数保持为引用(因此,如果在被指代对象的生存期结束后恢复协程,它可能变成悬垂引用),上述例如传入的是int型引用。
  3. 调用承诺对象的构造函数(promise)。如果承诺类型拥有接收所有协程形参的构造函数,那么以复制后的协程实参调用该构造函数。否则调用其默认构造函数。这里采用默认构造函数,没具名给出。
  4. 调用 promise.get_return_object() 并将其结果在局部变量中保持。该调用的结果将在协程首次暂停时返回给调用方。至此并包含这个步骤为止,任何抛出的异常均传播回调用方,而非置于承诺中。
  5. 调用 promise.initial_suspend() 。典型的承诺类型要么(对于惰性启动的协程)返回 std::suspend_always,要么(对于急切启动的协程)返回 std::suspend_never。
  •         *如果返回std::suspend_never,表示await表达式应该决不暂停,立即执行,不挂起
  •         *返回std::suspend_always,表示await表达式应该始终暂停,不立即执行,先挂起
  1. 当 co_await promise.initial_suspend() 恢复时,开始协程体的执行。

        【2】当协程抵达暂停点时:

  1. 将先前获得的返回对象返回给调用方/恢复方,这里是coroutine_first_test函数,如果需要则先隐式转换到协程的返回类型。
  2. 协程从暂停点返回通过co_yield,本质上是调用了promise.yield_value(表达式)
  3. 调用方/恢复方,coroutine_first_test函数通过调用coroutine_handle的resume告知协程恢复执行,协程函数coroutine_func重新从co_yield语句的下一句开始执行。

        【3】当协程抵达 co_return 语句时,它进行下列操作:

  1. 若是co_return,调用 promise.return_void(),如果承诺类型 Promise 没有 Promise::return_void() 成员函数(本例所采用),那么则行为未定义。
  2. 若是co_return expr,调用 promise.return_value(expr),其中 expr 具有非 void 类型或 void 类型,如果承诺类型 Promise 没有 Promise::return_value() 成员函数(本例子没定义),那么则行为未定义。
  3. 控制流出返回时,协程结束开始结束运行。此时以创建的逆序销毁所有具有自动存储期的变量。
  4. 调用 promise.final_suspend() 。

        【4】如果协程因未捕捉的异常结束,那么它进行下列操作:

  1. 捕捉异常并在 catch 块内调用 promise.unhandled_exception()
  2. 调用 promise.final_suspend() (例如,以恢复某个继续或发布其结果)。此时开始恢复协程是未定义行为。

        【5】协程当经由 co_return 或未捕捉异常而正常终止,它进行下列操作:

  1. 调用承诺对象的析构函数(~promise_type,默认析构)。
  2. 调用各个函数形参副本的析构函数(本例只有int型引用)。
  3. 调用 operator delete 以释放协程状态所用的内存(~task)。
  4. 转移执行回到调用方/恢复方(coroutine_first_test)。

         这里的协程函数返回对象采用的是 std::suspend_never等待体,标准库定义了两个平凡的可等待体:std::suspend_always 及 std::suspend_never,先说std::suspend_never:

//std::suspend_never,定义于头文件 <coroutine>,
//suspend_never 是空类,能用于指示 await 表达式绝不暂停并且不产生值。/*成员函数*/
/*(C++20 起) 
*std::suspend_never::await_ready,指示 await 表达式绝不暂停(公开成员函数) 
*始终返回 true ,指示 await 表达式绝不暂停。
*/
constexpr bool await_ready() const noexcept { return true; }/*(C++20 起) 
*std::suspend_never::await_suspend,无操作(公开成员函数) 
*不做任何事。
*/   
constexpr void await_suspend() const noexcept {}/*(C++20 起) 
*std::suspend_never::await_resume,无操作(公开成员函数) 
*不做任何事。若使用 suspend_never 则 await 表达式不产生值。
*/
constexpr void await_resume() const noexcept {} 

        编译g++ main.cpp test*.cpp -o test.exe -std=c++20,运行程序很好展示了上述逻辑过程:

         2.3 承诺类型(Promise)

        承诺类型(Promise),获得到承诺对象的引用。若 *this 不指代承诺对象尚未被销毁的协程,则行为未定义。此函数不对特化 std::coroutine_handle<> 提供。

//std::coroutine_handle<Promise>::promise
//主模板的成员
Promise& promise() const;
//特化 std::coroutine_handle<std::noop_coroutine_promise> 的成员
std::noop_coroutine_promise& promise() const noexcept; 

        编译器用 std::coroutine_traits 从协程的返回类型确定承诺类型。

正式而言,,如果定义它为非静态成员函数,以如下方式确定它的承诺类型 :

/*
*令 R 与 Args... 分别代表协程的返回类型与参数类型列表,
*ClassT 与 /*cv限定*/ (如果存在)分别代表协程所属的类与其 cv 限定
*/
std::coroutine_traits<R, Args...>::promise_type    //如果不定义协程为非静态成员函数。
std::coroutine_traits<R, ClassT /*cv限定*/&, Args...>::promise_type    //如果定义协程为非右值引用限定的非静态成员函数。
◦std::coroutine_traits<R, ClassT /*cv限定*/&&, Args...>::promise_type  //如果定义协程为右值引用限定的非静态成员函数。

        例如,如果上述结构体task定义为结构体模板,template<typename T>  struct task,其协程函数定义:

//如果定义协程为 
task<float> foo(std::string x, bool flag);
//那么它的承诺类型是 
std::coroutine_traits<task<float>, std::string, bool>::promise_type。//如果定义协程为 
task<void> my_class::method1(int x) const;
//那么它的承诺类型是 std::coroutine_traits<task<void>, const my_class&, int>::promise_type。//如果定义协程为 
task<void> my_class::method1(int x) &&;
//那么它的承诺类型是 
std::coroutine_traits<task<void>, my_class&&, int>::promise_type。

        每个协程均与下列对象关联:

  • 承诺(promise)对象。
  • 协程句柄 (coroutine handle)。
  • 协程状态 (coroutine state),它是一个包含以下各项的分配于堆(除非优化掉其分配)的内部对象:
  1. 承诺对象
  2. 各个形参(全部按值复制)
  3. 当前暂停点的某种表示,使得恢复时程序知晓要从何处继续,销毁时知晓有哪些局部变量在作用域内
  4. 生存期跨过当前暂停点的局部变量和临时量

        协程状态由非数组 operator new 在堆上分配。如果承诺类型定义了类级别的替代函数,那么会使用它,否则会使用全局的 operator new;如果承诺类型定义了接收额外形参的 operator new 的布置形式,且它们所匹配的实参列表中的第一实参是要求的大小(std::size_t 类型),而其余则是各个协程函数实参,那么将这些实参传递给 operator new(这使得能对协程使用前导分配器约定)

        如果分配失败,那么协程抛出 std::bad_alloc,除非承诺类型 Promise 类型定义了成员函数 Promise::get_return_object_on_allocation_failure()。如果定义了该成员函数,那么使用 operator new 的 nothrow 形式进行分配,而在分配失败时,协程会立即将从 Promise::get_return_object_on_allocation_failure() 获得的对象返回给调用方。

        2.4 承诺类型-协程返回类型及协程函数的交互

        下来看一下一个更复杂的例子,可以从协程传递( co_yield、co_return)回引用,实现协程与调用者的交互。

//test1.h
#ifndef _TEST_1_H_
#define _TEST_1_H_
void coroutine_model_test(void);
#endif //_TEST_1_H_
//test1.cpp
#include "test1.h"#include <coroutine>
#include <iostream>
#include <optional>
#include <ranges>template<typename T> requires std::movable<T>
class Task {
public://promise_type就是承诺对象,承诺对象用于协程内外交流struct promise_type {//生成协程返回,会在协程正在运行前进行调用Task<T> get_return_object() {std::cout << "get_return_object \n";return Task{Handle::from_promise(*this)};}/**返回的就是std::suspend_always,在协程被创建及真正运行前,被调用*/static std::suspend_always initial_suspend() noexcept {std::cout << "initial_suspend \n";return {}; }//返回awaiter,在协程最后退出后调用的接口。static std::suspend_always final_suspend() noexcept { std::cout << "final_suspend \n";return {}; }//返回awaiter,会在 co_yield v 时被调用类型就是T,v就是传入参数valuestd::suspend_always yield_value(T value) noexcept //-4-{current_value = std::move(value);std::cout << "yield_value ";return {};}//会在 co_return v 时被调用,把 co_return 后面跟着的值value作为参数传入,这里一般就是把这个值保存下来,提供给协程调用者void return_value(const T& value){std::cout << "return_value call "<< value << "\n";//cout危险操作,取决于T类型,这里为了展示原理current_value = std::move(value);return;}//会在 co_return v 时被调用,无传入值,和return_value只选一个,否则会报编译错误/*void return_void(){std::cout << "return void invoked." << std::endl;}*/// 生成器协程中不允许 co_await 。void await_transform() = delete;//协程内的代码抛出了异常,这个接口会被调用static void unhandled_exception() {std::cout << "unhandled_exception ";throw;}std::optional<T> current_value;};using Handle = std::coroutine_handle<promise_type>;//协程句柄explicit Task(Handle coroutine) : m_coroutine{coroutine}{}Task() = default;~Task() { std::cout << "~Task \n";if (m_coroutine) //自行销毁{m_coroutine.destroy(); }}Task(const Task&) = delete;Task& operator=(const Task&) = delete;Task(Task&& other) noexcept : m_coroutine{other.m_coroutine}{ other.m_coroutine = {}; }Task& operator=(Task&& other) noexcept {if (this != &other) {m_coroutine = other.m_coroutine;other.m_coroutine = {};}return *this;}// 基于范围的 for 循环支持。通过迭代操作实现协程应用class Iter {public:void operator++() //-6-{ std::cout << "real resume ";m_coroutine.resume(); //++时,恢复协程}const T& operator*() const { return *m_coroutine.promise().current_value; //取值,通过promise获取数据,返回值T}        bool operator==(std::default_sentinel_t) const { return !m_coroutine || m_coroutine.done(); //赋值时,协程执行}explicit Iter(Handle coroutine) : m_coroutine{coroutine}{}private:Handle m_coroutine;//协程句柄};//range应用指定的开闭区间Iter begin() {if (m_coroutine) {m_coroutine.resume();} return Iter{m_coroutine};}std::default_sentinel_t end() { return {}; }const T& get_val(){return *m_coroutine.promise().current_value;}
private:Handle m_coroutine; //协程句柄
};//协程函数的返回值为Task<T>类型,协程的返回类型必须内部定义Task<T>::promise_type
template<std::integral T>
Task<T> range(T first, T last)     
{T sum = T();while (first < last) //-2-{sum += first;co_yield first++;//协程会挂起,返回值;等价于co_await promise.yield_value(表达式)。-3-//调用者resume时,在此处恢复执行std::cout << "co_yield\n";//-7-}co_return sum;//等价于co_await promise.return_value(表达式)
};void coroutine_model_test(void)
{auto rs = range(-4, 4);for (int i : rs) //-1-,// for (int i : range(-4, 4)) //-1-,{std::cout << i << " ";//-5-}std::cout << "\n";std::cout << "rs last val = " << rs.get_val() << "\n";
};
//main.cpp
#include "test1.h"int main(int argc, char* argv[])
{coroutine_model_test();return 0;
};

       本案例定义了一个for循环的范围返回函数,该范围是Task类的begin()和end()函数提供,而函数引用了内置类型Iter,Iter在迭代递增时(operator++()),会调用std::coroutine_handle的resume进行协程恢复。协程运行到“co_yield first++;”时,就会通过yield_value设置了promise内部的缓存值,并返回,而调用者函数coroutine_model_test则通过std::coroutine_handle句柄获知promise承诺对象及内部值(及遍历数值),在遍历时,每次递增,本质上会调用std::coroutine_handle的resume告知协程恢复执行,而协程每次进行递增数值会写入promise承诺对象内部,如此反复等同于coroutine_model_test获得遍历范围值。

        协程最终返回时“co_return sum;”,传递回来一个数值,本质上是通过promise承诺对象内部return_value函数实现的。因为通过“current_value = std::move(value);”将最后传递进入的值保存在promise承诺对象内部,因此在调用函数内通过get_val就能取得该缓存的值。

    const T& Task<T>::get_val(){return *m_coroutine.promise().current_value;}

         上述例子中通过“-*-”标识了协程调用逻辑次序,编译g++ main.cpp test*.cpp -o test.exe -std=c++20,运行测试:

         Task内部promise 类的工作主要是两个:

  1. 从协程的承诺对象创建 coroutine_handle,接口是get_return_object。
  2. 是定义协程的执行流程,主要接口是initial_suspend,final_suspend。
  3. 是负责协程和调用者之间的数据传递,主要接口是 yield_value 和return_value或return_void。

        通常,promise_type类型需要主要实现这几个接口:

        【1】Task<T> get_return_object () 这个接口要能用 promise 自己的实例构造出一个协程的返回值,会在协程正在运行前进行调用,这个接口的返回值会作为协程的返回值。

        【2】std::suspend_always initial_suspend () 这个接口会在协程被创建(也就是第一次调用),真正运行前,被调用。在上述这个例子里,指定返回空类,指示 await 表达式始终暂停并且不产生值。

return {}; 

        std::suspend_always是一个结构体,前面已经说明了std::suspend_never,下来看看std::suspend_always,它和std::suspend_never几乎一样:

/* (C++20 起) 
*std::suspend_always,定义于头文件 <coroutine>
*suspend_always 是空类,能用于指示 await 表达式始终暂停并且不产生值。
*/
struct suspend_always;

        该类包含几个成员函数,用来判定

成员函数
/*(C++20)  指示 await 表达式始终暂停(公开成员函数) 
*std::suspend_always::await_ready
*始终返回 false ,指示 await 表达式始终暂停。
*/
constexpr bool await_ready() const noexcept { return false; }/*(C++20)  无操作(公开成员函数)
*std::suspend_always::await_suspend
*不做任何事。
*/
constexpr void await_suspend() const noexcept {}(C++20 起) /*(C++20)  无操作(公开成员函数) 
*std::suspend_always::await_resume
*不做任何事。若使用 suspend_always 则 await 表达式不产生值。
*/
constexpr void await_resume() const noexcept {}

        【3】std::suspend_always yield_value (T v) 这个接口会在 co_yield v 时被调用,把 co_yield 后面跟着的值 v 做为参数传入,这里一般就是把这个值保存下来,提供给协程的调用者,返回值一般是std::suspend_always {}。
        【4】void return_value (T v) 这个接口会在 co_return v 时被调用,把 co_return 后面跟着的值 v 作为参数传入,这里一般就是把这个值保存下来,提供给协程调用者。
        【5】void return_void () 如果 co_return 后面没有接任何值,那么就会调用这个接口。return_void 和return_value 只能选择一个实现,否则会报编译错误。
        【6】std::suspend_always final_suspend () 在协程最后退出后调用的接口,如果返回 std::suspend_always 。
        【7】协程结束后,则需要用户自行调用 coroutine_handle 的 destroy 接口来释放协程相关的资源。若协程对应的 handle 就已经为空,不能再调用 destroy 了 (会 coredump)。

        【8】void unhandled_exception () 如果协程内的代码抛出了异常,那么这个接口会被调用。

        std::coroutine_handle<promise_type> 是协程的控制句柄类,也是协程函数返回类型的最重要成员,通过标准库里std::coroutine_handle结构体定义,就可以实现与承诺对象的交互能力。恢复协程、销毁协程实例等都是通过该句柄实现。

        2.5 co_await

        一元运算符 co_await 暂停协程并将控制返回给调用方。它的操作数是一个函数表达式:

co_await 函数表达式   

        函数表达式,即函数,其返回一个类似于std::suspend_always可等待结构体(awaitable),就是需要像std::suspend_always一样为该结构体定义await_ready、await_suspend、await_resume函数:

  • 协程函数resuming_on_new_thread,调用函数表达式fun(switch_to_new_thread),fun返回结果,就是一个等待体(这里是awaitable)。
  • 开始调用 await_ready()。如果它的结果按语境转换成 bool 为 false,那么:暂停协程(以各局部变量和当前暂停点填充其协程状态),然后调用 await_suspend 接口,并将协程的句柄传给这个接口。
  • 如果await_ready 返回 true,那么协程完全不会被挂起,直接会去调用 await_resume () 接口,把这个接口作为 await 的返回值,继续执行协程。
  • 调用 await_suspend(handle),其中 handle 是表示当前协程的协程句柄。这个函数内部可以通过这个句柄观察暂停的协程,而且此函数负责调度它以在某个执行器上恢复,或将其销毁(并返回 false 当做调度) ◦
  • 如果 await_suspend 返回 void,那么立即将控制返回给当前协程的调用方/恢复方(此协程保持暂停),否则如果 await_suspend 返回 bool,那么:
    1. 值为 true 时将控制返回给当前协程的调用方/恢复方
    2. 值为 false 时恢复当前协程。
  • 如果 await_suspend 返回某个其他协程的协程句柄,那么(通过调用 handle.resume())恢复该句柄(注意这可以连锁进行,并最终导致当前协程恢复)
  • 如果 await_suspend 抛出异常,那么捕捉该异常,恢复协程,并立即重抛异常
  • 最后,调用 await_resume(),它的结果就是整个 co_await expr 表达式的结果。
  • 如果协程在 co_await 表达式中暂停而在后来恢复,那么恢复点处于紧接对 await_resume() 的调用之前。

        注意,因为协程在进入 await_suspend() 前已经完全暂停,所以该函数可以自由地在线程间转移协程柄而无需额外同步。例如,可以将它放入回调,将它调度成在异步 I/O 操作完成时在线程池上运行等。此时因为当前协程可能已被恢复,从而执行了等待器的析构函数,同时由于 await_suspend() 在当前线程上持续执行,await_suspend() 应该把 *this 当作已被销毁并且在柄被发布到其他线程后不再访问它。

          2.6 co_await案例

        下面例子,定义了一个awaitable,具有定义await_ready、await_suspend、await_resume函数成员函数,通过switch_to_new_thread函数表达式返回co_await。

//test2.h
#ifndef _TEST_2_H_
#define _TEST_2_H_
void coroutine_wait_test(void);
#endif //_TEST_2_H_
//test2.cpp
#include "test2.h"
#include <coroutine>
#include <iostream>
#include <stdexcept>
#include <thread>auto switch_to_new_thread(std::jthread& out) 
{struct awaitable {std::jthread* p_out;//co_await开始会调用,根据返回值决定是否挂起协程bool await_ready() { return false; }//在协程挂起后会调用这个,如果返回true,会返回调用者,如果返回false,会立刻resume协程void await_suspend(std::coroutine_handle<> h) {std::jthread& out = *p_out;if (out.joinable())throw std::runtime_error("jthread out arg is unull");out = std::jthread([h] { h.resume(); });//新建线程,并协程恢复std::cout << "new thread ID:" << out.get_id() << "\n"; //}//在协程resume的时候会调用这个,这个的返回值会作为await的返回值void await_resume() {}};return awaitable{&out};
}struct Task{struct promise_type {Task get_return_object() { return {}; }std::suspend_never initial_suspend() { return {}; }std::suspend_never final_suspend() noexcept { return {}; }void return_void() {}void unhandled_exception() {}};
};Task resuming_on_new_thread(std::jthread& out) {std::cout << "initial,ID:" << std::this_thread::get_id() << "\n";co_await switch_to_new_thread(out);//协程等待// 等待器在此销毁std::cout << "final,ID:" << std::this_thread::get_id() << "\n";
}void coroutine_wait_test(void)
{std::jthread out;auto ret = resuming_on_new_thread(out);
};
//main.cpp
#include "test2.h"int main(int argc, char* argv[])
{coroutine_wait_test();return 0;
};

       编译g++ main.cpp test*.cpp -o test.exe -std=c++20及运行程序:

         2.7 无操作协程

        前面描述协程支持库就提到过无操作协程,相比一般协程,它体现如此特征:在协程控制流外不做任何事,在开始和恢复后立即暂停,拥有一种协程状态,而销毁该状态为无操作,若有任何指代它的 std::coroutine_handle 则绝不抵达其最终暂停点。

无操作协程,定义于头文件 <coroutine>
noop_coroutine          (C++20)创建在等待或销毁时无可观察作用的协程柄(函数) 
noop_coroutine_promise  (C++20)用于无可观察作用的协程(类) 
noop_coroutine_handle   (C++20)std::coroutine_handle<std::noop_coroutine_promise> ,有意用于指代无操作协程
(typedef) 

        std::noop_coroutine_promise是是无操作协程的承诺类型,本质上就是一个前面讲述的空promise_type结构体:

//定义于头文件 <coroutine>
struct noop_coroutine_promise {}; 

        而std::noop_coroutine_handle就是std::coroutine_handle句柄以std::noop_coroutine_promise为承诺对象的特例化,

//定义于头文件 <coroutine>,(C++20 起) 
template< class Promise = void > struct coroutine_handle;template<> struct coroutine_handle<std::noop_coroutine_promise>;
using noop_coroutine_handle = std::coroutine_handle<std::noop_coroutine_promise>;

        std::noop_coroutine是一个函数,用来返回指代无操作协程的协程柄。

/*std::noop_coroutine,定义于头文件 <coroutine>,(C++20 起) 
*返回值指代无操作协程的 std::noop_coroutine_handle 
*若已有无操作协程的协程状态,则不指定 noop_coroutine 的后续调用是返回先前获得的协程柄,
*还是指代新的无操作协程的协程状态的协程柄。
*/
std::noop_coroutine_handle noop_coroutine() noexcept;

        2.8 无操作协程案例

        这是协程函数嵌套的例子,该例子里协程函数test调用了协程函数get_random,它们的返回值都是Task<int>。协程返回类型内,定义了一个可等待体awaiter,在co_await调用时开始触发。另外还为协程返回类型Task定义了承诺类型promise_type,及在该承诺类型内定义了一个可等待体final_awaiter,它会在承诺类型调用final_suspend时构建和开发触发。可等待体final_awaiter的await_suspend函数在传递协程句柄有效时直接返回,以恢复先前的协程;否则返回 noop_coroutine() ,其恢复不做任何事。

//test3.h
#ifndef _TEST_3_H_
#define _TEST_3_H_void coroutine_noop_test(void);#endif //_TEST_3_H_
//test3.cpp
#include "test3.h"
#include <coroutine>
#include <utility>
#include <iostream>template<class T>
struct Task {struct promise_type {//承诺类型promise_type() : result(T()),previous(std::noop_coroutine()){std::cout << "in Task::promise_type()\n";};auto get_return_object() {std::cout << "in Task::promise_type::get_return_object()\n";return Task(std::coroutine_handle<promise_type>::from_promise(*this));}//返回std::suspend_always{} ,表示await表达式应该始终暂停,不立即执行,先挂起std::suspend_always initial_suspend() { std::cout << "in Task::promise_type::initial_suspend()\n";return {}; }//可等待体定义struct final_awaiter {//co_await开始会调用,根据返回值决定是否挂起协程bool await_ready() noexcept(true) { std::cout << "in Task::promise_type::final_awaiter::await_ready()\n";return false; }//在协程挂起后会调用这个,如果返回true,会返回调用者,如果返回false,会立刻resume协程std::coroutine_handle<> await_suspend(std::coroutine_handle<promise_type> h) noexcept(true){// 在当前协程(以 'h' 指代)执行即将结束时调用 final_awaiter::await_suspend 。// 若当前协程被另一协程经由 co_await get_Task() 恢复,则存储到该协程的柄// 为 h.promise().previous 。该情况下,返回柄以恢复先前的协程。// 否则返回 noop_coroutine() ,其恢复不做任何事。std::cout << "in Task::promise_type::final_awaiter::await_suspend()\n";std::cout << "T = " << h.promise().result << "\n";//co_return *传递的值,不规范语句,主要为了测试逻辑展示auto previous = h.promise().previous;if (previous) {return previous;} else {return std::noop_coroutine();}}//在协程resume的时候会调用这个,这个的返回值会作为final_awaiter的返回值void await_resume() noexcept(true) {std::cout << "in Task::promise_type::final_awaiter::await_resume()\n";}};//返回final_awaiter{},Task结束协程时,将进入promise_type.final_suspend,进入final_awaiter等待体执行逻辑final_awaiter final_suspend() noexcept { std::cout << "in Task::promise_type::final_suspend()\n";return {}; }void unhandled_exception() { throw; }//会在 co_return v 时被调用,把这个v值保存下来,提供给协程调用者void return_value(T value) { std::cout << "in Task::promise_type::return_value()\n";result += std::move(value); }T result;std::coroutine_handle<> previous;};//Task(std::coroutine_handle<promise_type> h) : coro(h) {//get_return_object函数内调用std::cout << "in Task()\n";}Task(Task&& t) = delete;~Task() { std::cout << "in ~Task()\n";coro.destroy(); }//可等待体定义struct awaiter {//co_await开始会调用,根据返回值决定是否挂起协程bool await_ready() { std::cout << "in Task::awaiter::await_ready()\n";return false; //挂起}//在协程挂起后会调用这个,如果返回true,会返回调用者,如果返回false,会立刻resume协程auto await_suspend(std::coroutine_handle<> h) {std::cout << "in Task::awaiter::await_suspend()\n";coro.promise().previous = h;//将Task协程句柄指向的promise,其内部定义std::coroutine_handle<> 特例化句柄return coro;}//在协程resume的时候会调用这个,这个的返回值会作为awaiter的返回值T await_resume() { std::cout << "in Task::awaiter::await_resume()\n";return std::move(coro.promise().result); }std::coroutine_handle<promise_type> coro;};awaiter operator co_await() { //co_await调用std::cout << "in Task::co_await()\n";return awaiter{coro}; //将Task协程句柄传入}T operator()() {std::cout << "in Task::operator()\n";coro.resume();return std::move(coro.promise().result);}
private:std::coroutine_handle<promise_type> coro;//协程句柄
};//协程函数,返回Task<int>
Task<int> get_random() {std::cout << "in get_random()\n";co_return 4;
};//协程函数,返回Task<int>
Task<int> test() {Task<int> v = get_random(); //Task<int> u = get_random();std::cout << "in test()\n";int x = (co_await v + co_await u);//相当于调用Task::co_await()co_return x;
};void coroutine_noop_test(void)
{Task<int> t = test();//int result = t();std::cout << result << '\n';
};
//main.cpp
#include "test3.h"int main(int argc, char* argv[])
{coroutine_noop_test();return 0;
};

        编译g++ main.cpp test3.cpp -o test.exe -std=c++20,运行测试:

http://www.15wanjia.com/news/3810.html

相关文章:

  • wordpress设置网站地图日本产品和韩国产品哪个好
  • 安阳做网站推广最好的公司品牌策划公司排名
  • 网站建设有哪些软件有哪些内容全国疫情最新公布
  • 怎样用jsp做网站 新手教程苏州seo报价
  • 做网站空间500m多少钱广州快速排名
  • 植物网站建设百度游戏app下载
  • 怎么做网站的浏览量昨日凌晨北京突然宣布重大消息
  • 同一个地方做几个网站什么软件比百度搜索好
  • 网站租用服务器费用上海优化公司
  • 如何拍做美食的视频网站交换链接案例
  • 自己编写的网站如何放到wordpress优化用户体验
  • 买公司 网站建设武汉seo优化服务
  • 网站上做百度广告赚钱么专业的网站优化公司排名
  • 做网站流量要钱吗项目营销推广策划
  • 不良网站正能量进入窗口网络黄页推广软件
  • 网站版面风格微信小程序开发工具
  • 网站弹出代码企业网站的域名是该企业的
  • 电子商务网站建设主管的策划案东莞网站推广宣传
  • 北京网站建设有哪些公司好百度产品推广怎么收费
  • 移动端网站建设重点有哪些爱站网关键词挖掘查询工具
  • wordpress h5主题seo优化博客
  • 做新闻源网站采集站赚钱网络推广费用高吗
  • 临朐网站建设价格备案域名购买
  • 网站建设找哪家好免费b站推广网站短视频
  • 长沙网站建设哪家公司好百度推广首次开户需要多少钱
  • 广州网站推广方案哪家竞价托管专业
  • a站与b站深圳百度竞价推广
  • 企业网站也需要在公安做备案么考证培训机构
  • 禅城网站建设哪家好今天的头条新闻
  • 汉寿做网站的公司企业营销策划包括哪些内容