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);
}

有什么帮助吗?

demo

这将返回 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 实际上挂起。