Cur*_*ous 21 c++ asynchronous coroutine c++20 c++-coroutine
我试图阅读有关在调用,暂停,恢复和终止协程函数时调用的操作顺序的文档(功能部件本身的cppreference和标准文档)。该文档深入概述了各种扩展点,这些扩展点允许库开发人员使用库组件自定义协程的行为。从高级的角度来看,这种语言功能似乎是经过深思熟虑的。
不幸的是,我在遵循协程执行机制方面确实很难,而且作为图书馆开发人员,我如何使用各种扩展点来定制所述协程的执行。甚至从哪里开始。
我不完全了解的新定制点集中包含以下功能:
initial_suspend()return_void()return_value()await_ready()await_suspend()await_resume()final_suspend()unhandled_exception()有人可以在高级伪代码中描述运行用户协程时编译器生成的代码吗?在抽象的层面,我想弄清楚的时候功能,如await_suspend,await_resume,await_ready,await_transform,return_value,等叫,他们所服务的目的是什么,我怎么可以用它们来写协同程序库。
不确定这是否是题外话,但是这里的一些入门资源对于整个社区来说都是非常有用的。像在cppcoro中一样四处搜寻并深入研究库实现并不能帮助我克服最初的障碍:(
dor*_*ron 15
N4775概述了针对C ++ 20的协程的建议。它介绍了许多不同的想法。以下是我的博客,网址为https://dwcomputersolutions.net。更多信息可以在我的其他帖子中找到。
在检查整个Hello World协程程序之前,请逐步进行各个步骤。这些包括:
整个文件包含在这篇文章的结尾。
Future f()
{
co_return 42;
}
Run Code Online (Sandbox Code Playgroud)
我们用实例化协程
Future myFuture = f();
Run Code Online (Sandbox Code Playgroud)
这是一个简单的协程,只返回值42。这是一个协程,因为它包含关键字co_return。具有关键字的任何功能
co_await,co_return或者co_yield是一个协程。
您会注意到的第一件事是,尽管我们返回的是整数,但协程返回类型是(用户定义的)Future类型。原因是,当我们调用协程时,我们现在不运行该函数,而是初始化一个对象,该对象最终将为我们提供我们正在寻找Future的值。
实例化协程时,编译器要做的第一件事是找到表示此协程特定类型的promise类型。
通过创建以下模板的部分专业化,我们告诉编译器什么promise类型属于什么协程函数签名
template <typename R, typename P...>
struct coroutine_trait
{};
with a member called `promise_type` that defines our Promise Type
Run Code Online (Sandbox Code Playgroud)
对于我们的示例,我们可能想要使用类似以下的内容:
template<>
struct std::experimental::coroutines_v1::coroutine_traits<Future> {
using promise_type = Promise;
};
Run Code Online (Sandbox Code Playgroud)
在这里,我们创建了一个coroutine_trait没有指定参数和返回类型的特殊化Future,这与我们的协程函数签名完全匹配
Future f(void)。promise_type然后是诺言类型,在我们的例子中是struct Promise。
现在是用户了,coroutine_trait
因为协程库提供了一种很好的简单方法来指定promise_typeFuture类本身,所以我们通常不会创建自己的专业化名称。以后再说。
正如我在上一篇文章中提到的那样,由于协程是可挂起的和可恢复的,因此局部变量不能总是存储在堆栈中。为了存储非堆栈安全的局部变量,编译器将在堆上分配一个Context对象。我们的Promise实例也会被存储。
协程通常是无用的,除非它们能够与外界通信。我们的承诺告诉我们协程应该如何表现,而我们的未来对象允许其他代码与协程进行交互。然后,“承诺与未来”将通过我们的协程进行沟通。
一个简单的协程承诺看起来像:
struct Promise
{
Promise() : val (-1), done (false) {}
std::experimental::coroutines_v1::suspend_never initial_suspend() { return {}; }
std::experimental::coroutines_v1::suspend_always final_suspend() {
this->done = true;
return {};
}
Future get_return_object();
void unhandled_exception() { abort(); }
void return_value(int val) {
this->val = val;
}
int val;
bool done;
};
Future Promise::get_return_object()
{
return Future { Handle::from_promise(*this) };
}
Run Code Online (Sandbox Code Playgroud)
如前所述,当协程被实例化并在协程的整个生命周期中退出时,将分配promise。
完成后,编译器将调用get_return_object此用户定义的函数,该函数负责创建Future对象并将其返回给协程实例化器。
在我们的实例中,我们希望我们的Future能够与协程进行通信,因此我们使用协程的手柄来创建Future。这将使我们的未来能够访问我们的承诺。
创建协程后,我们需要知道是要立即开始运行它还是要立即保持挂起状态。这是通过调用Promise::initial_suspend()函数来完成的。此函数返回一个Awaiter,我们将在另一篇文章中查找。
在我们的例子中,由于我们确实希望函数立即启动,因此我们调用
suspend_never。如果我们暂停了该函数,则需要通过在句柄上调用resume方法来启动协程。
我们需要知道co_return在协程中调用运算符时该怎么做。这是通过return_value功能完成的。在这种情况下,我们将值存储在Promise中,以便以后通过Future检索。
如果发生异常,我们需要知道该怎么做。这是通过unhandled_exception函数完成的
。因为在我们的示例中,不应发生异常,所以我们只是中止了。
最后,我们需要知道在破坏协程之前该怎么做。这是通过进行的。final_suspend function在这种情况下,由于我们要检索结果,所以我们返回suspend_always。然后必须通过协程处理destroy方法销毁协程。否则,如果我们返回
suspend_never,协程将在运行结束后立即销毁。
手柄可以访问协程及其承诺。有两种味道,当我们不需要访问promise时使用void句柄,当我们需要访问promise时使用带有promise类型的协程句柄。
template <typename _Promise = void>
class coroutine_handle;
template <>
class coroutine_handle<void> {
public:
void operator()() { resume(); }
//resumes a suspended coroutine
void resume();
//destroys a suspended coroutine
void destroy();
//determines whether the coroutine is finished
bool done() const;
};
template <Promise>
class coroutine_handle : public coroutine_handle<void>
{
//gets the promise from the handle
Promise& promise() const;
//gets the handle from the promise
static coroutine_handle from_promise(Promise& promise) no_except;
};
Run Code Online (Sandbox Code Playgroud)
未来看起来像这样:
class [[nodiscard]] Future
{
public:
explicit Future(Handle handle)
: m_handle (handle)
{}
~Future() {
if (m_handle) {
m_handle.destroy();
}
}
using promise_type = Promise;
int operator()();
private:
Handle m_handle;
};
int Future::operator()()
{
if (m_handle && m_handle.promise().done) {
return m_handle.promise().val;
} else {
return -1;
}
}
Run Code Online (Sandbox Code Playgroud)
Future对象负责将协程抽象到外部世界。我们有一个构造函数,根据诺言的get_return_object实现从诺言中获取句柄。
破坏者会破坏协程,因为在我们的案例中,控制就是承诺的生命。
最后我们有一行:
using promise_type = Promise;
Run Code Online (Sandbox Code Playgroud)
coroutine_trait如果我们promise_type在协程的返回类中定义我们的代码,那么C ++库将使我们免于实现自己的实现。
我们终于得到它了。我们的第一个简单协程。
#include <experimental/coroutine>
#include <iostream>
struct Promise;
class Future;
using Handle = std::experimental::coroutines_v1::coroutine_handle<Promise>;
struct Promise
{
Promise() : val (-1), done (false) {}
std::experimental::coroutines_v1::suspend_never initial_suspend() { return {}; }
std::experimental::coroutines_v1::suspend_always final_suspend() {
this->done = true;
return {};
}
Future get_return_object();
void unhandled_exception() { abort(); }
void return_value(int val) {
this->val = val;
}
int val;
bool done;
};
class [[nodiscard]] Future
{
public:
explicit Future(Handle handle)
: m_handle (handle)
{}
~Future() {
if (m_handle) {
m_handle.destroy();
}
}
using promise_type = Promise;
int operator()();
private:
Handle m_handle;
};
Future Promise::get_return_object()
{
return Future { Handle::from_promise(*this) };
}
int Future::operator()()
{
if (m_handle && m_handle.promise().done) {
return m_handle.promise().val;
} else {
return -1;
}
}
//The Co-routine
Future f()
{
co_return 42;
}
int main()
{
Future myFuture = f();
std::cout << "The value of myFuture is " << myFuture() << std::endl;
return 0;
}
Run Code Online (Sandbox Code Playgroud)
该co_await运营商允许我们暂停我们的协同程序和返回控制返回给调用者协程。这使我们可以在等待操作完成的同时做其他工作。完成后,我们可以从上次中断的地方继续恢复。
co_await运算符可以通过多种方式来处理其右侧的表达式。现在,我们将考虑最简单的情况,那就是
co_await表达式返回一个Awaiter的地方。
一个Awaiter是一个简单的struct或class实现了以下方法:await_ready,await_suspend和await_resume。
bool await_ready() const {...}只是返回是否准备好恢复协程或是否需要暂停协程。假设
await_ready返回false。我们开始跑步await_suspend
该await_suspend方法有几个签名。最简单的是void
await_suspend(coroutine_handle<> handle) {...}。这是我们co_await将暂停的协程对象的句柄。此函数完成后,控制权将返回给协程对象的调用方。正是这个功能负责存放协程手柄,以便以后使协程不会永远保持悬浮状态。
一旦handle.resume()被调用;await_ready返回false;或其他某种机制恢复我们的协程,auto await_resume()则调用该方法。的返回值await_resume是co_await操作员返回的值。有时,如上所述,expr in co_await expr返回一个等待者是不切实际的。如果expr返回一个类,则该类可以提供自己的Awaiter operator co_await (...) which will return the Awaiter.
Alternatively one can implement anawait_transform method in ourpromise_type 实例,该实例会将
expr转换为Awaiter。
既然我们已经对Awaiter进行了描述,我想指出的是,我们两个返回Awaiter 的
initial_suspend和final_suspend方法promise_type。对象suspend_always和suspend_never是琐碎的等待者。
suspend_always返回true await_ready,suspend_never返回false。但是,没有什么可以阻止您推出自己的产品。
如果您想知道现实生活中的侍应生是什么样子,请看一下我未来的对象。它将协程手柄存储在一个lamda中,以供以后处理。