您好,欢迎来到年旅网。
搜索
您的当前位置:首页C++ 20 协程(三)

C++ 20 协程(三)

来源:年旅网

C++ 20 协程(三)

可等待体和等待器

promise_type中的三个函数返回可等待体 yield_value, initial_suspend, final_suspend

可等待体

可等待体决定协程是否暂停

本质上,编译器使用promise和co_await操作符生成这三个函数调用。

co_await需要一个可等待体作为参数

实现可等待体需要三个函数

C++20标准已经定义了两个基本的对象:std::suspend_alwaysstd::suspend_never

The Awaitable std::suspend_always

struct suspend_always {
constexpr bool await_ready() const noexcept { return false; }
constexpr void await_suspend(std::coroutine_handle<>) const noexcept {}
constexpr void await_resume() const noexcept {}
};

总是挂起,await_ready返回false

The Awaitable std::suspend_never

struct suspend_never {
constexpr bool await_ready() const noexcept { return true; }
constexpr void await_suspend(std::coroutine_handle<>) const noexcept {}
constexpr void await_resume() const noexcept {}
};

从不挂起,await_ready返回true

当协程协程执行的时候,这两个函数会自动执行:

  • 开始 initial_suspend
  • 结束 final_suspend

initial_suspend

当initial_suspend返回suspend_always时,协程会在开始时挂起;返回suspend_never时,则不会挂起

A lazy coroutine

std::suspend_always initial_suspend() {
return {};
}

A eager coroutine

std::suspend_never initial_suspend() {
return {};
}

final_suspend

在协程结束时执行,与几乎initial_suspend相同

可等待体和等待器

  • 可被等待的对象称为可等待(awaitable )体或者表达式;
  • co_await运算符必须返回一个等待器(awaiter):
    • 可等待体和等待器可以是同一个类型;
    • std::future(实验)是可等待体。
    • co_await运算符返回等待器_Future_awaiter

工作流

编译器执行两个工作流外部的promise工作流和内部的awaiter工作流

Promise 工作流

当在函数中使用co_yield, co_await, co_return,函数成为一个协程,并且编译器将其转换成等价的如下代码

The transformed coroutine

主要工作步骤:

  • 协程开始执行:
    • 申请必要的协程帧空间
    • 拷贝所有函数参数到协程帧
    • 创建promise_type对象
    • 调用promise_type中的get_return_object方法创建协程句柄(coroutine handle),并保持在局部变量中,当协程第一次挂起时,将返回给调用者
    • 调用initial_suspend并且co_await其结果,可能返回suspend_always/never
    • co_await prom.initial_suspend恢复resume时,函数体执行
  • 协程到达挂起点:
    • 返回对象(prom.get_return_object())将返回给恢复协程的调用程序
  • 协程到达co_return
    • 调用prom.retrun_void/value没有返回值或者返回值
    • 销毁变量
    • 调用prom.final_suspend()并且co_await它的结果
  • 协程销毁
    • 调用promise_type对象和函数参数对象的析构函数
    • 释放协程帧的内存
    • 返还控制权给调用者
  • 调用co_await执行等待器工作流

Awaiter工作流

调用co_await会让编译器执行三个函数:await_ready await_suspend await_resume

The generated Awaiter Workflow

只有await_ready返回false时,流程才会执行,否则的话直接返回await_resume的结果

await_ready返回false时:

  • 协程挂起,计算awaitable.await_suspend()的返回值,返回值有很多种情况

出现异常情况不想写了

co_return

协程使用co_return作为返回语句

template <typename T>
struct MyFuture
{
	std::shared_ptr<T> value;

	MyFuture(std::shared_ptr<T> p): value(p)
	{
	}

	~MyFuture()
	{
	}

	T get()
	{
		return *value;
	}

	struct promise_type
	{
		std::shared_ptr<T> ptr = std::make_shared<T>();

		~promise_type()
		{
		}

		MyFuture<T> get_return_object() { return ptr; }

		void return_value(T v) { *ptr = v; }

		std::suspend_never initial_suspend() { return {}; }

		std::suspend_never final_suspend() noexcept { return {}; }

		void unhandled_exception()
		{
			std::terminate();
		}
	};
};

MyFuture<int> createFuture()
{
	co_return 2021;
}

int main(int argc, char* argv[])
{
	auto fut = createFuture();

	std::cout << fut.get() << std::endl;
}

  • 流程
    • 初始化协程
      • 申请必要的协程帧空间
      • 拷贝所有函数参数到协程帧
      • 创建promise_type对象
      • 调用promise_type中的get_return_object方法将ptr传给fut
    • 调用co_return
      • 调用return_value并传入参数2022
    • 输出fut.get()

co_yield

无限数据流

template <typename T>
struct Generator
{
	struct promise_type;
	using handle_type = std::coroutine_handle<promise_type>;


	Generator(handle_type h): coro(h)
	{
	}

	handle_type coro;

	~Generator() { if (coro) coro.destroy(); }

	Generator(const Generator&) = delete;
	Generator& operator =(const Generator&) = delete;

	Generator(Generator&& oth) noexcept : coro(oth.coro)
	{
		oth.coro = nullptr;
	}

	Generator& operator =(Generator&& oth) noexcept
	{
		coro = oth.coro;
		oth.coro = nullptr;
		return *this;
	}

	T getValue()
	{
		return coro.promise().current_value;
	}

	bool next()
	{
		coro.resume();
		return !coro.done();
	}

	struct promise_type
	{
		promise_type() = default;
		~promise_type() = default;

		auto initial_suspend()
		{
			return std::suspend_always{};
		}

		auto final_suspend() noexcept
		{
			return std::suspend_always{};
		}

		auto get_return_object()
		{
			return Generator{handle_type::from_promise(*this)};
		}

		auto return_void()
		{
			return std::suspend_never{};
		}

		auto yield_value(const T value)
		{
			current_value = value;
			return std::suspend_always{};
		}

		void unhandled_exception()
		{
			std::terminate();
		}

		T current_value;
	};
};

Generator<int> getNext(int start = 0, int step = 1)
{
	auto value = start;
	while (true)
	{
		co_yield value;
		value += step;
	}
}

int main(int argc, char* argv[])
{
	auto gen = getNext();
	for (int i = 0; i <= 10; ++i)
	{
		gen.next();
		std::cout << std::format("gen value: {}\n", gen.getValue());
	}

	std::cout << "\n\n";

	auto gen2 = getNext(100, -10);
	for (int i = 0; i <= 20; ++i)
	{
		gen2.next();
		std::cout << std::format("gen2 value: {}\n", gen2.getValue());
	}
}

看一下执行流程;

  • 创建promise_type
  • 调用get_return_object(),将其结果保存在局部变量
  • 创建generator
  • 调用initial_suspend挂起协程
  • 请求下一个值并消耗一个值然后挂起
  • 接着调用gen.next重复循环

因篇幅问题不能全部显示,请点此查看更多更全内容

Copyright © 2019- oldu.cn 版权所有 浙ICP备2024123271号-1

违法及侵权请联系:TEL:199 1889 7713 E-MAIL:2724546146@qq.com

本站由北京市万商天勤律师事务所王兴未律师提供法律服务