概要
本文介绍如何实例化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完成修改,但是如果非标准库,其两种方式都可完成。让我们一起开始协程之旅