通过多个嵌套协程挂起
Supsending thrugh multiple nested coroutines
我试图低估新协程在 C++20 中的工作原理,但除了非常琐碎的示例外,我无法使其工作。
我的目标是创建深层嵌套函数,允许最内部的函数中断并 return 控制大多数外部代码,并在某些情况下将控制权交还给该内部函数。
这是有效的 setjmp
和 longjmp
.
我使用在网络中找到的一些示例搞砸了一些代码:
#include <iostream>
#include <coroutine>
#include <optional>
template <typename T>
struct task
{
struct task_promise;
using promise_type = task_promise;
using handle_type = std::coroutine_handle<promise_type>;
mutable handle_type m_handle;
task(handle_type handle)
: m_handle(handle)
{
}
task(task&& other) noexcept
: m_handle(other.m_handle)
{
other.m_handle = nullptr;
}
bool await_ready()
{
return m_handle.done();
}
bool await_suspend(std::coroutine_handle<> handle)
{
if (!m_handle.done()) {
m_handle.resume();
}
return !m_handle.done();
}
auto await_resume()
{
return result();
}
T result() const
{
if (!m_handle.done())
m_handle.resume();
return *m_handle.promise().m_value;
}
//manualy wait for finish
bool one_step()
{
if (!m_handle.done())
m_handle.resume();
return !m_handle.done();
}
~task()
{
if (m_handle)
m_handle.destroy();
}
struct task_promise
{
std::optional<T> m_value {};
auto value()
{
return m_value;
}
auto initial_suspend()
{
return std::suspend_always{};
}
auto final_suspend()
{
return std::suspend_always{};
}
auto return_value(T t)
{
m_value = t;
return std::suspend_always{};
}
task<T> get_return_object()
{
return {handle_type::from_promise(*this)};
}
void unhandled_exception()
{
std::terminate();
}
void rethrow_if_unhandled_exception()
{
}
};
};
static task<int> suspend_one()
{
std::cout<< "suspend_one in\n";
co_await std::suspend_always();
std::cout<< "suspend_one return\n";
co_return 1;
}
static task<int> suspend_two()
{
std::cout<< "suspend_two -> suspend_one #1\n";
auto a = co_await suspend_one();
std::cout<< "suspend_two -> suspend_one #2\n";
auto b = co_await suspend_one();
std::cout<< "suspend_two return\n";
co_return a + b;
}
static task<int> suspend_five()
{
std::cout<< "suspend_five -> suspend_two #1\n";
auto a = co_await suspend_two();
std::cout<< "suspend_five -> suspend_one #2\n";
auto b = co_await suspend_one();
std::cout<< "suspend_five -> suspend_two #3\n";
auto c = co_await suspend_two();
std::cout<< "suspend_five return\n";
co_return a + b + c;
}
static task<int> run()
{
std::cout<< "run -> suspend_two #1\n";
auto a = co_await suspend_two();
std::cout<< "run -> suspend_one #2\n";
auto b = co_await suspend_one();
std::cout<< "run -> suspend_five #3\n";
auto c = co_await suspend_five();
std::cout<< "run -> suspend_one #4\n";
auto d = co_await suspend_one();
std::cout<< "run -> suspend_two #5\n";
auto e = co_await suspend_two();
std::cout<< "run return\n";
co_return a + b + c + d + e;
}
int main()
{
std::cout<< "main in\n";
auto r = run();
std::cout<< "main -> while\n";
while (r.one_step()){ std::cout<< "<<<< while loop\n"; }
std::cout<< "main return\n";
return r.result();
}
https://gcc.godbolt.org/z/JULJCi
函数 run
按预期工作,但我遇到了问题,例如suspend_five
永远达不到线的地方
std::cout<< "suspend_five -> suspend_two #3\n";
可能我的 task
版本已完全损坏,但我不知道在哪里可以找到此错误或它应该是什么样子。
或者只是 C++20 不支持我想要实现的东西? co_yeld
可能是解决方法的候选者,因为它看起来更有可能手动嵌套它们 (for (auto z : f()) co_yeld z;
),但这个问题的目标是了解解决某些现有问题的 C++20 功能的内部机制。
经过一些挖掘和阅读文档后,我得出的结论是代码如下:
//manualy wait for finish
bool one_step()
{
if (!m_handle.done())
m_handle.resume();
return !m_handle.done();
}
完全坏掉了,做的很落后。如果内部任务没有完成,也就是 m_handle.inner.done()
,你不能 m_handle.resume()
。
这意味着我们需要先“移动”最内部的任务,然后才能移动外部任务。
现在这是“有效”的新版本,可能我在那里遗漏了一些细节,但至少结果与我预期的相似。
#include <cstdio>
#include <coroutine>
#include <optional>
namespace
{
template <typename T>
struct task
{
struct task_promise;
using promise_type = task_promise;
using handle_type = std::coroutine_handle<promise_type>;
mutable handle_type m_handle;
task(handle_type handle)
: m_handle(handle)
{
}
task(task&& other) noexcept
: m_handle(other.m_handle)
{
other.m_handle = nullptr;
}
bool await_ready()
{
//check need for coroutine that do not have `co_await`
return !m_handle || m_handle.done();
}
bool await_suspend(std::coroutine_handle<> handle)
{
return true;
}
bool await_suspend(std::coroutine_handle<promise_type> handle)
{
handle.promise().m_inner_handler = m_handle;
m_handle.promise().m_outer_handler = handle;
return true;
}
auto await_resume()
{
return *m_handle.promise().m_value;
}
//manualy wait for finish
bool one_step()
{
auto curr = m_handle;
while (curr)
{
if (!curr.promise().m_inner_handler)
{
while (!curr.done())
{
curr.resume();
if (!curr.done())
{
return true;
}
if (curr.promise().m_outer_handler)
{
curr = curr.promise().m_outer_handler;
curr.promise().m_inner_handler = nullptr;
}
else
{
return false;
}
}
break;
}
curr = curr.promise().m_inner_handler;
}
return !curr.done();
}
~task()
{
if (m_handle)
m_handle.destroy();
}
struct task_promise
{
std::optional<T> m_value {};
std::coroutine_handle<promise_type> m_inner_handler {};
std::coroutine_handle<promise_type> m_outer_handler {};
auto value()
{
return m_value;
}
auto initial_suspend()
{
return std::suspend_never{};
}
auto final_suspend()
{
return std::suspend_always{};
}
auto return_value(T t)
{
m_value = t;
return std::suspend_always{};
}
task<T> get_return_object()
{
return {handle_type::from_promise(*this)};
}
void unhandled_exception()
{
std::terminate();
}
void rethrow_if_unhandled_exception()
{
}
};
};
task<int> suspend_none()
{
std::printf("suspend_none\n");
co_return 0;
}
task<int> suspend_one()
{
std::printf("suspend_one \\n");
co_await std::suspend_always();
std::printf("suspend_one /\n");
co_return 1;
}
task<int> suspend_two()
{
co_await suspend_none();
auto a = co_await suspend_one();
co_await suspend_none();
auto b = co_await suspend_one();
co_return a + b;
}
task<int> suspend_five()
{
auto a = co_await suspend_two();
auto b = co_await suspend_two();
co_return 1 + a + b;
}
task<int> run()
{
std::printf("run\n");
auto a = co_await suspend_five();
auto b = co_await suspend_five();
auto c = co_await suspend_five();
co_return 5 + a + b + c;
}
}
int main()
{
std::printf( "main in\n");
auto r = run();
std::printf( "main -> while\n");
while (r.one_step()){ std::printf(" while loop\n"); }
std::printf( "main return\n");
return r.await_resume();
}
https://gcc.godbolt.org/z/f8zKPqK1d
最重要的是:
bool await_suspend(std::coroutine_handle<promise_type> handle)
{
handle.promise().m_inner_handler = m_handle;
m_handle.promise().m_outer_handler = handle;
return true;
}
我 link 将每一帧放在一起,并允许我们在进入“堆栈上层”之前推动最内部的一帧。
我认为这可以被认为是一个穷人的 stackfull 协程。
PS:
更新示例以包括处理没有 co_await
的协程
我试图低估新协程在 C++20 中的工作原理,但除了非常琐碎的示例外,我无法使其工作。
我的目标是创建深层嵌套函数,允许最内部的函数中断并 return 控制大多数外部代码,并在某些情况下将控制权交还给该内部函数。
这是有效的 setjmp
和 longjmp
.
我使用在网络中找到的一些示例搞砸了一些代码:
#include <iostream>
#include <coroutine>
#include <optional>
template <typename T>
struct task
{
struct task_promise;
using promise_type = task_promise;
using handle_type = std::coroutine_handle<promise_type>;
mutable handle_type m_handle;
task(handle_type handle)
: m_handle(handle)
{
}
task(task&& other) noexcept
: m_handle(other.m_handle)
{
other.m_handle = nullptr;
}
bool await_ready()
{
return m_handle.done();
}
bool await_suspend(std::coroutine_handle<> handle)
{
if (!m_handle.done()) {
m_handle.resume();
}
return !m_handle.done();
}
auto await_resume()
{
return result();
}
T result() const
{
if (!m_handle.done())
m_handle.resume();
return *m_handle.promise().m_value;
}
//manualy wait for finish
bool one_step()
{
if (!m_handle.done())
m_handle.resume();
return !m_handle.done();
}
~task()
{
if (m_handle)
m_handle.destroy();
}
struct task_promise
{
std::optional<T> m_value {};
auto value()
{
return m_value;
}
auto initial_suspend()
{
return std::suspend_always{};
}
auto final_suspend()
{
return std::suspend_always{};
}
auto return_value(T t)
{
m_value = t;
return std::suspend_always{};
}
task<T> get_return_object()
{
return {handle_type::from_promise(*this)};
}
void unhandled_exception()
{
std::terminate();
}
void rethrow_if_unhandled_exception()
{
}
};
};
static task<int> suspend_one()
{
std::cout<< "suspend_one in\n";
co_await std::suspend_always();
std::cout<< "suspend_one return\n";
co_return 1;
}
static task<int> suspend_two()
{
std::cout<< "suspend_two -> suspend_one #1\n";
auto a = co_await suspend_one();
std::cout<< "suspend_two -> suspend_one #2\n";
auto b = co_await suspend_one();
std::cout<< "suspend_two return\n";
co_return a + b;
}
static task<int> suspend_five()
{
std::cout<< "suspend_five -> suspend_two #1\n";
auto a = co_await suspend_two();
std::cout<< "suspend_five -> suspend_one #2\n";
auto b = co_await suspend_one();
std::cout<< "suspend_five -> suspend_two #3\n";
auto c = co_await suspend_two();
std::cout<< "suspend_five return\n";
co_return a + b + c;
}
static task<int> run()
{
std::cout<< "run -> suspend_two #1\n";
auto a = co_await suspend_two();
std::cout<< "run -> suspend_one #2\n";
auto b = co_await suspend_one();
std::cout<< "run -> suspend_five #3\n";
auto c = co_await suspend_five();
std::cout<< "run -> suspend_one #4\n";
auto d = co_await suspend_one();
std::cout<< "run -> suspend_two #5\n";
auto e = co_await suspend_two();
std::cout<< "run return\n";
co_return a + b + c + d + e;
}
int main()
{
std::cout<< "main in\n";
auto r = run();
std::cout<< "main -> while\n";
while (r.one_step()){ std::cout<< "<<<< while loop\n"; }
std::cout<< "main return\n";
return r.result();
}
https://gcc.godbolt.org/z/JULJCi
函数 run
按预期工作,但我遇到了问题,例如suspend_five
永远达不到线的地方
std::cout<< "suspend_five -> suspend_two #3\n";
可能我的 task
版本已完全损坏,但我不知道在哪里可以找到此错误或它应该是什么样子。
或者只是 C++20 不支持我想要实现的东西? co_yeld
可能是解决方法的候选者,因为它看起来更有可能手动嵌套它们 (for (auto z : f()) co_yeld z;
),但这个问题的目标是了解解决某些现有问题的 C++20 功能的内部机制。
经过一些挖掘和阅读文档后,我得出的结论是代码如下:
//manualy wait for finish
bool one_step()
{
if (!m_handle.done())
m_handle.resume();
return !m_handle.done();
}
完全坏掉了,做的很落后。如果内部任务没有完成,也就是 m_handle.inner.done()
,你不能 m_handle.resume()
。
这意味着我们需要先“移动”最内部的任务,然后才能移动外部任务。
现在这是“有效”的新版本,可能我在那里遗漏了一些细节,但至少结果与我预期的相似。
#include <cstdio>
#include <coroutine>
#include <optional>
namespace
{
template <typename T>
struct task
{
struct task_promise;
using promise_type = task_promise;
using handle_type = std::coroutine_handle<promise_type>;
mutable handle_type m_handle;
task(handle_type handle)
: m_handle(handle)
{
}
task(task&& other) noexcept
: m_handle(other.m_handle)
{
other.m_handle = nullptr;
}
bool await_ready()
{
//check need for coroutine that do not have `co_await`
return !m_handle || m_handle.done();
}
bool await_suspend(std::coroutine_handle<> handle)
{
return true;
}
bool await_suspend(std::coroutine_handle<promise_type> handle)
{
handle.promise().m_inner_handler = m_handle;
m_handle.promise().m_outer_handler = handle;
return true;
}
auto await_resume()
{
return *m_handle.promise().m_value;
}
//manualy wait for finish
bool one_step()
{
auto curr = m_handle;
while (curr)
{
if (!curr.promise().m_inner_handler)
{
while (!curr.done())
{
curr.resume();
if (!curr.done())
{
return true;
}
if (curr.promise().m_outer_handler)
{
curr = curr.promise().m_outer_handler;
curr.promise().m_inner_handler = nullptr;
}
else
{
return false;
}
}
break;
}
curr = curr.promise().m_inner_handler;
}
return !curr.done();
}
~task()
{
if (m_handle)
m_handle.destroy();
}
struct task_promise
{
std::optional<T> m_value {};
std::coroutine_handle<promise_type> m_inner_handler {};
std::coroutine_handle<promise_type> m_outer_handler {};
auto value()
{
return m_value;
}
auto initial_suspend()
{
return std::suspend_never{};
}
auto final_suspend()
{
return std::suspend_always{};
}
auto return_value(T t)
{
m_value = t;
return std::suspend_always{};
}
task<T> get_return_object()
{
return {handle_type::from_promise(*this)};
}
void unhandled_exception()
{
std::terminate();
}
void rethrow_if_unhandled_exception()
{
}
};
};
task<int> suspend_none()
{
std::printf("suspend_none\n");
co_return 0;
}
task<int> suspend_one()
{
std::printf("suspend_one \\n");
co_await std::suspend_always();
std::printf("suspend_one /\n");
co_return 1;
}
task<int> suspend_two()
{
co_await suspend_none();
auto a = co_await suspend_one();
co_await suspend_none();
auto b = co_await suspend_one();
co_return a + b;
}
task<int> suspend_five()
{
auto a = co_await suspend_two();
auto b = co_await suspend_two();
co_return 1 + a + b;
}
task<int> run()
{
std::printf("run\n");
auto a = co_await suspend_five();
auto b = co_await suspend_five();
auto c = co_await suspend_five();
co_return 5 + a + b + c;
}
}
int main()
{
std::printf( "main in\n");
auto r = run();
std::printf( "main -> while\n");
while (r.one_step()){ std::printf(" while loop\n"); }
std::printf( "main return\n");
return r.await_resume();
}
https://gcc.godbolt.org/z/f8zKPqK1d
最重要的是:
bool await_suspend(std::coroutine_handle<promise_type> handle)
{
handle.promise().m_inner_handler = m_handle;
m_handle.promise().m_outer_handler = handle;
return true;
}
我 link 将每一帧放在一起,并允许我们在进入“堆栈上层”之前推动最内部的一帧。
我认为这可以被认为是一个穷人的 stackfull 协程。
PS:
更新示例以包括处理没有 co_await