C++20 协程。调用 yield 时检索空值
C++20 coroutines. When yield is called empty value is retrieved
我看了Björn Fahller - Asynchronous I/O and coroutines for smooth data streaming - Meeting C++ online的演讲。在这个演示之后,我尝试自己执行一个类似的例子。我的代码中有一个错误,当调用 yield 时,打印的值为零。调试代码,我检测到 yield_value 与 await_resume 相比,是从不同的 promise 对象调用的。
我很困惑,我不知道如何使用正确的承诺对象调用 yield_value。
#include <iostream>
#include <coroutine>
#include <optional>
#include <string>
#include <memory>
using namespace std;
template<typename T>
struct promise;
template<typename T>
struct task
{
using promise_type = promise<T>;
auto operator co_await() const noexcept
{
struct awaitable
{
awaitable(promise<T> & promise)
:m_promise(promise)
{
}
bool await_ready() const noexcept
{
return m_promise.isready();
}
void await_suspend(coroutine_handle<promise_type> next)
{
m_promise.m_continuation = next;
}
T await_resume() const
{
std::cout << "await_resume m_promise::" << &m_promise << std::endl;
return m_promise.get();
}
promise<T> & m_promise;
};
return awaitable(_coroutine.promise());
}
task(promise_type& promise) : _coroutine(coroutine_handle<promise_type>::from_promise(promise))
{
promise.m_continuation = _coroutine;
}
task() = default;
task(task const&) = delete;
task& operator=(task const&) = delete;
task(task && other) : _coroutine(other._coroutine)
{
other._coroutine = nullptr;
}
task& operator=(task&& other)
{
if (&other != this) {
_coroutine = other._coroutine;
other._coroutine = nullptr;
}
return *this;
}
static task<T> make()
{
std::cout << "Enter make" << std::endl;
co_await suspend_always{};
std::cout << "Enter exit" << std::endl;
}
auto get_promise()
{
std::cout << "get_promise " << &_coroutine.promise() << std::endl;
return _coroutine.promise();
}
~task()
{
if (_coroutine) {
_coroutine.destroy();
}
}
private:
friend class promise<T>;
coroutine_handle<promise_type> _coroutine;
};
template<typename T>
struct promise
{
task<T> get_return_object() noexcept
{
return {*this};
}
suspend_never initial_suspend() noexcept{return {};}
suspend_always final_suspend() noexcept{return {};}
bool isready() const noexcept
{
return m_value.has_value();
}
T get()
{
return m_value.has_value()? m_value.value(): 0;
}
void unhandled_exception()
{
auto ex = std::current_exception();
std::rethrow_exception(ex);
//// MSVC bug? should be possible to rethrow with "throw;"
//// rethrow exception immediately
// throw;
}
template<typename U>
suspend_always yield_value(U && u)
{
std::cout << "yield_value::" << &m_continuation.promise() << std::endl;
m_value.emplace(std::forward<U>(u));
m_continuation.resume();
//m_continuation.
return {};
}
void return_void(){}
coroutine_handle<promise<T>> m_continuation;
optional<T> m_value;
};
template<typename T>
task<T> print_all(task<T> & values)
{
std::cout << "print all" << std::endl;
for(;;)
{
auto v = co_await values;
std::cout << v << "\n" << std::flush;
}
}
int main(int argc, const char * argv[]) {
auto incoming = task<int>::make();
auto h = print_all(incoming);
auto promise = incoming.get_promise();
promise.yield_value(4);
}
有什么帮助吗?
这将返回 promise
:
的 副本
auto get_promise()
{
std::cout << "get_promise " << &_coroutine.promise() << std::endl;
return _coroutine.promise();
}
因此,不是为 task
调用 the promise
,而是调用其他一些不相关的 promise
对象.
一旦你解决了这个问题,你会发现你的代码有一个无限循环。当它有一个值时,你的承诺就“准备好了”。但是一旦它有一个值,它就会一直有一个值——它总是准备好的。解决此问题的一种方法是确保 await_resume
消耗该值。例如,将 get()
更改为:
T get()
{
assert(m_value.has_value());
T v = *std::move(m_value);
m_value.reset();
return v;
}
这确保 下一个 co_await
实际上挂起。
我看了Björn Fahller - Asynchronous I/O and coroutines for smooth data streaming - Meeting C++ online的演讲。在这个演示之后,我尝试自己执行一个类似的例子。我的代码中有一个错误,当调用 yield 时,打印的值为零。调试代码,我检测到 yield_value 与 await_resume 相比,是从不同的 promise 对象调用的。 我很困惑,我不知道如何使用正确的承诺对象调用 yield_value。
#include <iostream>
#include <coroutine>
#include <optional>
#include <string>
#include <memory>
using namespace std;
template<typename T>
struct promise;
template<typename T>
struct task
{
using promise_type = promise<T>;
auto operator co_await() const noexcept
{
struct awaitable
{
awaitable(promise<T> & promise)
:m_promise(promise)
{
}
bool await_ready() const noexcept
{
return m_promise.isready();
}
void await_suspend(coroutine_handle<promise_type> next)
{
m_promise.m_continuation = next;
}
T await_resume() const
{
std::cout << "await_resume m_promise::" << &m_promise << std::endl;
return m_promise.get();
}
promise<T> & m_promise;
};
return awaitable(_coroutine.promise());
}
task(promise_type& promise) : _coroutine(coroutine_handle<promise_type>::from_promise(promise))
{
promise.m_continuation = _coroutine;
}
task() = default;
task(task const&) = delete;
task& operator=(task const&) = delete;
task(task && other) : _coroutine(other._coroutine)
{
other._coroutine = nullptr;
}
task& operator=(task&& other)
{
if (&other != this) {
_coroutine = other._coroutine;
other._coroutine = nullptr;
}
return *this;
}
static task<T> make()
{
std::cout << "Enter make" << std::endl;
co_await suspend_always{};
std::cout << "Enter exit" << std::endl;
}
auto get_promise()
{
std::cout << "get_promise " << &_coroutine.promise() << std::endl;
return _coroutine.promise();
}
~task()
{
if (_coroutine) {
_coroutine.destroy();
}
}
private:
friend class promise<T>;
coroutine_handle<promise_type> _coroutine;
};
template<typename T>
struct promise
{
task<T> get_return_object() noexcept
{
return {*this};
}
suspend_never initial_suspend() noexcept{return {};}
suspend_always final_suspend() noexcept{return {};}
bool isready() const noexcept
{
return m_value.has_value();
}
T get()
{
return m_value.has_value()? m_value.value(): 0;
}
void unhandled_exception()
{
auto ex = std::current_exception();
std::rethrow_exception(ex);
//// MSVC bug? should be possible to rethrow with "throw;"
//// rethrow exception immediately
// throw;
}
template<typename U>
suspend_always yield_value(U && u)
{
std::cout << "yield_value::" << &m_continuation.promise() << std::endl;
m_value.emplace(std::forward<U>(u));
m_continuation.resume();
//m_continuation.
return {};
}
void return_void(){}
coroutine_handle<promise<T>> m_continuation;
optional<T> m_value;
};
template<typename T>
task<T> print_all(task<T> & values)
{
std::cout << "print all" << std::endl;
for(;;)
{
auto v = co_await values;
std::cout << v << "\n" << std::flush;
}
}
int main(int argc, const char * argv[]) {
auto incoming = task<int>::make();
auto h = print_all(incoming);
auto promise = incoming.get_promise();
promise.yield_value(4);
}
有什么帮助吗?
这将返回 promise
:
auto get_promise()
{
std::cout << "get_promise " << &_coroutine.promise() << std::endl;
return _coroutine.promise();
}
因此,不是为 task
调用 the promise
,而是调用其他一些不相关的 promise
对象.
一旦你解决了这个问题,你会发现你的代码有一个无限循环。当它有一个值时,你的承诺就“准备好了”。但是一旦它有一个值,它就会一直有一个值——它总是准备好的。解决此问题的一种方法是确保 await_resume
消耗该值。例如,将 get()
更改为:
T get()
{
assert(m_value.has_value());
T v = *std::move(m_value);
m_value.reset();
return v;
}
这确保 下一个 co_await
实际上挂起。