【问题标题】:C++ coroutines: call a coroutine function without co_awaitC++ 协程:调用协程函数而不使用 co_await
【发布时间】:2020-04-13 12:51:46
【问题描述】:

struct 任务由https://github.com/Quuxplusone/coro/blob/master/include/coro/gor_task.h 修改。我只是将suspend_always in Line 18 更改为suspend_never。

// test.cpp
#include <exception>
#include <experimental/coroutine>
#include <variant>

template<class T>
struct task {
    struct promise_type {
        std::variant<std::monostate, T, std::exception_ptr> result_;
        std::experimental::coroutine_handle<void> waiter_;

        task get_return_object() { return task(this); }
        auto initial_suspend() { return std::experimental::suspend_never{}; } // Originally suspend_always
        auto final_suspend() {
            struct Awaiter {
                promise_type *me_;
                bool await_ready() { return false; }
                void await_suspend(std::experimental::coroutine_handle<void> caller) {
                    me_->waiter_.resume();
                }
                void await_resume() {}
            };
            return Awaiter{this};
        }
        template<class U>
        void return_value(U&& u) {
            result_.template emplace<1>(static_cast<U&&>(u));
        }
        void unhandled_exception() {
            result_.template emplace<2>(std::current_exception());
        }
    };

    bool await_ready() { return false; }
    void await_suspend(std::experimental::coroutine_handle<void> caller) {
        coro_.promise().waiter_ = caller;
        coro_.resume();
    }
    T await_resume() {
        if (coro_.promise().result_.index() == 2) {
            std::rethrow_exception(std::get<2>(coro_.promise().result_));
        }
        return std::get<1>(coro_.promise().result_);
    }

    ~task() {
        coro_.destroy();
    }
private:
    using handle_t = std::experimental::coroutine_handle<promise_type>;
    task(promise_type *p) : coro_(handle_t::from_promise(*p)) {}
    handle_t coro_;
};

#include <stdio.h>

task<int> f2() {
    puts("enter f2");
    co_return 1;
}

task<int> f1() {
    puts("enter f1");
    int a = co_await f2();
    printf("f2 return: %d\n", a);
    co_return a;
}

int main() {
    f1();
}
$ clang++ -fcoroutines-ts -std=c++17 -stdlib=libc++ -lc++ -lc++abi test.cpp -o test
$ ./test
enter f1
enter f2
fish: './test' terminated by signal SIGSEGV (Address boundary error)

因为没有co_await,我希望应该打印f2 return: 1,并且程序应该正常退出,但它会因段错误而崩溃。为什么以及如何解决此问题?

【问题讨论】:

    标签: c++ coroutine c++20


    【解决方案1】:

    当协程函数执行co_await 时,它会暂停该函数的执行并将该执行的恢复安排给其他人。这个“其他人”最终取决于被co_awaited on 的表达式、协程函数的promise 类型以及协程返回的future 类型。

    那么让我们看看这里的控制流。

    f2 被调用。它执行,然后通过co_return 终止。这意味着f2 的协程句柄是完整的。这也意味着将调用承诺类型的final_suspend。好吧,协程机制期望 final_suspend 将返回一个等待类型,这是您的 task::promise 提供的。

    除了... task::promise::waiter_ 未初始化。这是因为只有await_suspend 将值分配给waiter_。还没有人在f2 的返回值上拥有co_awaited。所以在这种情况发生之前,waiter_ 没有值。

    所以在调用f2 的那一刻,您尝试继续一个充其量为nullptr 的协程句柄,从而导致崩溃。

    如果final_suspendAwaiter 类型首先检查waiter_ 是否为nullptr 会更有意义(这意味着没有人在等待协程),如果是,则返回true from await_ready.

    【讨论】:

    • 我将task::promise_type::final_suspend::Awaiter::await_ready 更改为return !me_-&gt;waiter_;,但仍然崩溃。
    • 我也试过让Awaiter::await_suspend返回布尔值。无论返回哪个值,程序都会崩溃。
    • @CarterLi:可能还有其他问题,但这是我发现的第一个问题。总的来说,我发现自己对你在这里想要完成的事情感到困惑。例如,你为什么要在await_suspend 中执行coro_.resume();,这个函数的名字就是关于suspending,而不是进一步执行协程?
    • 你是对的。 coro_.resume() in await_suspend 因为原来的版本是懒惰调用的。我忘了删除这条线。感谢您的帮助!
    猜你喜欢
    • 2020-07-28
    • 1970-01-01
    • 2021-04-23
    • 2020-07-26
    • 1970-01-01
    • 1970-01-01
    • 2021-01-26
    • 1970-01-01
    • 2022-11-22
    相关资源
    最近更新 更多