【问题标题】:Should the suspended coroutine handle be always destroyed before program ends? [duplicate]挂起的协程句柄是否应该总是在程序结束之前被销毁? [复制]
【发布时间】:2021-11-25 00:18:28
【问题描述】:

看看这个简化的例子:

std::coroutine_handle<> logger;
const char* next_msg = nullptr;
void log(const char* msg)
{
    next_msg = msg;
    if (logger) logger.resume();
}

struct wait_msg {
    bool await_ready() {
        return next_msg != nullptr;
    }
    void await_suspend(std::coroutine_handle<> h) {
        logger = h;
    }
    auto await_resume() {
        const char* msg = next_msg;
        next_msg = nullptr;
        return msg;
    }
};

struct procedure {
    struct promise_type;
    using handle_type = std::coroutine_handle<promise_type>;
    
    handle_type handle;

    struct promise_type {
        procedure get_return_object() { 
            return { handle_type::from_promise(*this)};
        }
        std::suspend_never initial_suspend() { return {}; }
        std::suspend_never final_suspend() noexcept { return {}; }
        void return_void() {}
        void unhandled_exception() { std::terminate(); }
    };
};

procedure log_messages()
{
    int c = 1;
    for (;;) {
        const char* msg = co_await wait_msg{};
        std::cout << c++ << ": " << msg << std::endl;
    }
}

假设我在初始或最终点没有暂停,并且协程在 co_await 上暂停,协程句柄是否应该在程序结束之前销毁?

或者:p.handle.destroy(); 行可以从main 中删除吗?

int main() {
    log("Hello World!");

    auto p = log_messages();
    
    log("Hello World, again!");
    log("Hello World, and again!");
    
    // is the next line needed?
    p.handle.destroy();
}

查看完整的demo

有问题的代码的先前版本here

【问题讨论】:

    标签: c++ destructor c++20 coroutine


    【解决方案1】:

    挂起的协程句柄是否应该总是在程序结束之前销毁?

    ,有些情况下destroy() 是不需要的,甚至是不可能的。

    是的,在问题代码中,协程句柄上的destroy() 是必需的。

    standard 的这句话解释了事情:

    当控制流结束时,协程状态被破坏 协程或破坏成员函数 ([coroutine.handle.resumption]) 的协程句柄 ([coroutine.handle]) 引用协程被调用。

    由于这个协程永远不会退出无限循环 (for(;;)) - 它必须通过它的句柄来销毁。

    procedure log_messages()
    {
        int c = 1;
        for (;;) {
            const char* msg = co_await wait_msg{};
            std::cout << c++ << ": " << msg << std::endl;
        }
    }
    
    

    但是,代码可以稍作修改 - 比如 - 当没有更多行要打印时返回 nullptr,并在 nullptr 上中断循环:

    procedure log_messages()
    {
        int c = 1;
        for (;;) {
            const char* msg = co_await wait_msg{};
            if (!msg) break; // !!!
            std::cout << c++ << ": " << msg << std::endl;
        }
    }
    
    
    

    在这种情况下 - destroy() 是不需要的,甚至是不可能的,因为它会导致 UB。 这是因为destroy() 只能在挂起的协程上调用,而我们在相关代码的最后一点没有挂起:

        struct promise_type {
            //...
            std::suspend_never final_suspend() noexcept { return {}; }
            //...
        };
    };
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-11-27
      • 2012-09-25
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-07-03
      相关资源
      最近更新 更多