C++20协程Promise

概要

本文介绍如何实例化Promise关键类,以及实现代码,熟悉C++协程框架的同学肯定都知道,协程框架提出了三个关键类,Promise、Awaitable以及Awaiter,后两者关系非常微妙,可以将二者合在一起,也可以只需要Awaiter。但Promise不同,它一定存在,并且在编译的时候就已经由编译器决定如何实例化,后续简称为P

编译器如何生成P类型

P是一个用户定义的类,用于控制和定义协程的行为。它是协程的核心部分,用于实现协程的状态机和管理协程的执行,生成方式如图:
在这里插入图片描述
通过上图我们知道,编译器会优先使用std::coroutine_traits去生成P类型,如果生成失败,则会通过co_await expr中的expr表达式发挥类型中找到嵌套的promise_type(expr::promise_type)。这里我们不能排除某些编译器有默认的P生成方式,虽然上面的图没有体现编译器默认实现。这里给一段伪代码

task<int> fun2() {
	std::cout << "fun2\n";
	co_return 4;
}
task<int> fun() {
	std::cout << "fun\n";
	auto result = co_await fun2();
	co_return result;
}

int main() {
	fun();
	return 0;
}

我们可以看到co_await expr中表达式为task<int>,后面我们将基于这个expr来详解P的生成。

promise_type嵌套生成方式详解

template<typename T>
class [[nodiscard]] task {
public:
	using promise_type = task_promise<T>;
	using value_type = T;
	task() noexcept : m_coroutine(nullptr) {}
	explicit task(std::coroutine_handle<promise_type> coroutine) : m_coroutine(coroutine) {}
	~task()
	{
		if (m_coroutine)
		{
			m_coroutine.destroy();
		}
	}

	auto operator co_await() const& noexcept {
		return awaitable{ m_coroutine };
	}

private:
	std::coroutine_handle<promise_type> m_coroutine;
};

从代码中我们可以看到我们在task里面添加了using promise_type = task_promise; 当然,我们也可以直接在task里面定义,可以将其替换成class promise_type,编译器会自动识别这里面的promise_type, 类似于task::promise_type

std::coroutine_traits生成方式详解

template<typename T>
struct std::coroutine_traits<task<T>> {
	using promise_type = task_promise<T>;
};

template<typename T>
class [[nodiscard]] task {
public:
	using value_type = T;
	task() noexcept : m_coroutine(nullptr) {}

	explicit task(std::coroutine_handle<typename std::coroutine_traits<task>::promise_type> coroutine) : m_coroutine(coroutine) {}
	~task()
	{
		if (m_coroutine)
		{
			m_coroutine.destroy();
		}
	}

	auto operator co_await() const& noexcept {
		return awaitable{ m_coroutine };
	}

private:
	std::coroutine_handle<typename std::coroutine_traits<task>::promise_type> m_coroutine;
};

可以看到,首先我们定义了struct std::coroutine_traits<task>的偏特化,其具体定义可参考coroutine_traits,然后我们在偏特化里面定义了promise_type,到这里coroutine_traits就完成了,编译器会去自动查找,除了这里我们还需要修改task,因为其promise_type不在内部了,所以我们需要使用coroutine_traits里面的类型,typename std::coroutine_traits::promise_type

promise_type

template<typename T>
class task_promise {
public:
	task_promise() noexcept : m_state(false) {}
	auto initial_suspend() noexcept
	{
		return std::suspend_never{};
	}

	T result()
	{
		return std::move(m_value);
	}
	friend struct final_awaitable;
	struct final_awaitable
	{
		bool await_ready() const noexcept { return false; }
		template<typename PROMISE>
		void await_suspend(std::coroutine_handle<PROMISE> coroutine) noexcept
		{
			task_promise& promise = coroutine.promise();
			if (promise.m_state.exchange(true, std::memory_order_acq_rel))
			{
				promise.m_continuation.resume();
			}
		}

		void await_resume() noexcept {}
	};

	auto final_suspend() noexcept
	{

		return final_awaitable{};
	}

	bool try_set_continuation(std::coroutine_handle<> continuation)
	{
		m_continuation = continuation;
		return !m_state.exchange(true, std::memory_order_acq_rel);
	}

	task<T> get_return_object() noexcept { return task<T> { std::coroutine_handle<task_promise>::from_promise(*this) }; }

	void unhandled_exception() noexcept {}

	template<typename VALUE>
	void return_value(VALUE&& value) {
		m_value = value;
	}

private:
	std::coroutine_handle<> m_continuation;
	std::atomic<bool> m_state;
	T m_value;
};

awaitable

template<typename PROMISE>
struct awaitable {
	std::coroutine_handle<PROMISE> m_coroutine;
	awaitable(std::coroutine_handle<PROMISE> coroutine) noexcept
		: m_coroutine(coroutine)
	{}
	bool await_ready() const noexcept
	{
		return !m_coroutine || m_coroutine.done();
	}

	bool await_suspend(std::coroutine_handle<> awaitingCoroutine) noexcept
	{
		m_coroutine.resume();
		return m_coroutine.promise().try_set_continuation(awaitingCoroutine);
	}

	decltype(auto) await_resume()
	{
		return this->m_coroutine.promise().result();
	}
};

小结

我们已经知道了promise_type如何生成以及基本协程框架的实现代码,在具体的生产环境中我们需要灵活切换,比如扩展标准库去实现协程,那我我们就需要通过coroutine_traits完成修改,但是如果非标准库,其两种方式都可完成。让我们一起开始协程之旅

C++20 引入了一种新的语言特性:协程协程是一种轻量级的、非抢占式的线程,可以实现在同一个线程中切换执行不同的任务,从而避免了线程切换的开销。C++20 中的协程可以通过 co_await 和 co_yield 等关键字来实现。 在 C++20 中创建协程的方式如下: 1. 定义协程函数,使用 co_await 来等待其他协程或异步操作完成: ```c++ #include <coroutine> struct task { struct promise_type { auto initial_suspend() { return std::suspend_never{}; } auto final_suspend() { return std::suspend_never{}; } auto get_return_object() { return task{}; } void return_void() {} void unhandled_exception() {} }; }; task foo() { std::cout << "start\n"; co_await std::suspend_always{}; std::cout << "end\n"; } int main() { foo(); } ``` 2. 使用 co_yield 来暂停当前协程,并返回一个值: ```c++ #include <coroutine> #include <iostream> struct generator { struct promise_type { int current_value; auto initial_suspend() { return std::suspend_always{}; } auto final_suspend() { return std::suspend_always{}; } auto get_return_object() { return generator{this}; } auto yield_value(int value) { current_value = value; return std::suspend_always{}; } void return_void() {} void unhandled_exception() {} }; generator(promise_type* p) : coro(std::coroutine_handle<promise_type>::from_promise(*p)) {} ~generator() { if (coro) coro.destroy(); } generator(const generator&) = delete; generator& operator=(const generator&) = delete; generator(generator&& other) noexcept : coro(other.coro) { other.coro = nullptr; } generator& operator=(generator&& other) noexcept { if (this != &other) { coro = other.coro; other.coro = nullptr; } return *this; } int next() { coro.resume(); return coro.promise().current_value; } private: std::coroutine_handle<promise_type> coro; }; generator range(int from, int to) { for (int i = from; i < to; ++i) { co_yield i; } } int main() { for (auto i : range(0, 5)) { std::cout << i << '\n'; } } ```
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值