I watched the Björn Fahller - Asynchronous I/O and coroutines for smooth data streaming - Meeting C online talk. Following up this presentation, I gave a try to execute a similar example myself. There is a bug in my code and when yield is called , the value that is printed is zero. Debugging the code , I detected that the yield_value comparing with await_resume, is called from different promise object. I am confused, and I do not know how to call the yield_value using the correct promise object.
#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);
}
Any help?
CodePudding user response:
This is returning a copy of the promise
:
auto get_promise()
{
std::cout << "get_promise " << &_coroutine.promise() << std::endl;
return _coroutine.promise();
}
So instead of calling into the promise
for the task
, you're calling into just some other, unrelated promise
object.
Once you fix that, you'll find that your code has an infinite loop. Your promise is "ready" when it has a value. But once it has a value, it always has a value - it's always ready. One way to fix this is to ensure that await_resume
consumes the value. For instance, by changing get()
to:
T get()
{
assert(m_value.has_value());
T v = *std::move(m_value);
m_value.reset();
return v;
}
That ensures that the next co_await
actually suspends.