【问题标题】:Can `co_yield` return a value from the caller on resumption of the coroutine?`co_yield` 可以在协程恢复时从调用者返回一个值吗?
【发布时间】:2023-03-09 20:52:01
【问题描述】:

C++20 引入了协程,可以用来创建生成器和其他类似的东西:

generator<int> counter(int max) {
    for(int i = 0; i < max; i++) {
        co_yield i;
    }
}

有什么方法可以创建一个协程,以便调用者可以在协程恢复后提供由co_yield 返回的响应?我们称之为channel 而不是生成器。

这是我想要做的一个例子:

channel<int, int> accumulator(int initial) {
    while(true) {
        // Can channel be written so co_yield provides a response?
        int response = co_yield initial;
        initial += response;
    }
}

这里,每当调用者恢复协程时,它都会提供一个值,然后在协程恢复后从co_yield 返回,如下所示:

std::vector<int> accumulate(std::vector<int> values) {
    channel<int, int> acc = accumulator(0);

    std::vector<int> summed_values;

    for(int v : values) {
        // Get whatever value was yielded by the accumulator
        int sum = acc.recieve();
        // Do something with the value
        summed_values.push_back(sum);
        // Resume the accumulator, returning this value from co_yield:
        acc.send(v); 
    }
    return summed_values;
}

根据评论编辑

谁能提供一些指导或示例来说明如何做到这一点?协程对我来说仍然很新。我有一个 channel 类的基本实现,但我不确定应该从 yield_value 返回什么来实现这一点。

我在 cmets 中标记了 (A)(B) 的两个位置。

template <class Out, class In>
struct channel {
    struct promise_type {
        Out current_value;
        auto yield_value(Out value) {
            current_value = value;
            // (A) What do I return here?
        }
        channel get_return_object() {
            return {std::coroutine_handle<promise_type>::from_promise(*this)};
        }
        // We run up until the first value is ready
        auto initial_suspend() noexcept { return std::suspend_never(); }
        auto final_suspend() noexcept { return std::suspend_always(); }
        void unhandled_exception() noexcept { std::terminate(); }
    };


    Out receive() {
        return handle.promise().current_value;
    }
    void send(In response) {
        // (B) What do I do here?
    }
    // Constructors, destructor and move assignment operator omitted for brevity
   private:
    std::coroutine_handle<promise_type> handle = nullptr;
};

【问题讨论】:

  • 是的。 co_yield v 的返回值是co_await p.yield_value(v) 的返回值,其中p 是协程承诺。因此,调用者可以调用 promise 上的自定义方法(您将其称为 send)以将值反馈到协程中,然后 yield_value() 方法返回一个输出该值的等待器。
  • 非常感谢您的回复。你能给我一些关于实施的指导吗?我已经更新了问题以包括我到目前为止的内容!我只标记了两个位置 A 和 B 我不知道如何填写
  • send 可以使用handle.promise() 访问promise_type 并将值放入新的promise 成员变量中。 yield_value 然后返回一个产生该值的等待者。

标签: c++ coroutine c++20


【解决方案1】:

key为await_resume,在awaiter上调用(yield_value的结果)获取co_yield的结果。

您还需要将响应存储在某处。正如 Raymond Chen 在comment 中建议的那样,您可以将值放入promise_type 的新数据成员中。

所以变化是:

  1. 将数据成员添加到promise_type

    In response;
    
  2. 定义一个自定义的等待者来返回该数据成员。

    struct awaiter : std::suspend_always {
        friend promise_type;
        constexpr In await_resume() const { return m_p->response; }
    
    private:
        constexpr awaiter(promise_type* p) : m_p(p) {}
        promise_type* m_p;
    };
    
  3. (A)中,返回自定义的等待者。

    return awaiter(this);
    
  4. (B)中,设置数据成员,然后恢复协程。

    handle.promise().response = response;
    handle.resume();
    

【讨论】:

  • 优秀的答案!谢谢!
猜你喜欢
  • 1970-01-01
  • 2020-12-22
  • 2017-02-15
  • 1970-01-01
  • 1970-01-01
  • 2020-10-15
  • 2011-02-04
  • 2017-06-11
  • 1970-01-01
相关资源
最近更新 更多