Home > Mobile >  C 20 coroutines. When yield is called empty value is retrieved
C 20 coroutines. When yield is called empty value is retrieved

Time:12-31

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?

demo

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.

  • Related