【问题标题】:Do temporaries passed to a function that returns awaitable remains valid after suspension point with co_await传递给返回等待的函数的临时对象在使用 co_await 的暂停点后是否仍然有效
【发布时间】:2020-06-29 09:38:10
【问题描述】:

我正在基于 windows io 完成端口的异步套接字类中添加对协程 ts 的支持。 如果没有协程,io 可能会像这样完成:

sock.async_write(io::buffer(somebuff), [](auto&& ... args){ /* in handler */ });

sock.async_write(std::vector<io::const_buffer>{ ... }, [](auto&& ... args){ /* in handler */ })

其中每个都会返回void,并通过handler通知结果,不需要缓存参数,因为从函数返回时操作已经提交

但是对于协程,该函数将返回一个可等待对象,在使用 operator co_await 等待它时将提交操作,因此我需要将参数缓存在可等待对象中以避免使用已破坏的临时对象:

awaitable coro_write(const io::const_buffer& buff)
{
    return awaitable{ *this, buff }; 
}

awaitable coro_write(const std::vector<io::const_buffer>& buffs)
{
    return awaitable{ *this, buffs };
}

第一个副本不会造成伤害,但第二个会造成伤害,因为它会触发堆分配并复制向量内容。

所以我正在寻找解决方案,在阅读此页面 coroutines ts 时,我遇到了这个:

典型的生成器的 yield_value 会将其参数存储(复制/移动或仅存储地址,因为参数的生命周期跨越 co_await 内的暂停点)到生成器对象中并返回 std::suspend_always,将控制权转移给调用者/简历。

并且在同一页面中指出co_yield 表达式等同于:

co_await promise.yield_value(expr)

这也类似于:

co_await sock.coro_write(expr)

我打开了Visual Studio 2019附带的生成器头,看到它也将参数的地址存储到yield_value,并在协程暂停后通过调用方站点中的generator::iterator::operator *()稍后检索:

struct promise_type {
    _Ty const* _CurrentValue;
     auto yield_value(_Ty const& _Value) {
         _CurrentValue = _STD addressof(_Value);
         return suspend_always{};
     }
}

struct iterator {
    _NODISCARD reference operator*() const {
        return *_Coro.promise()._CurrentValue;
    }
    _NODISCARD pointer operator->() const {
        return _Coro.promise()._CurrentValue;
    }
}

由此我得出结论,传递给返回与co_await 一起使用的等待器的函数的参数也将保持有效,直到协程恢复或销毁,对吗?或者这对于 Promise 类型中的 yield_value 是特殊的?

【问题讨论】:

    标签: c++ coroutine c++20 c++-coroutine


    【解决方案1】:

    用 Visual Studio 2019 16.4.4 测试,它可以工作。事实上,传递的参数和转换的参数也保持有效,直到await_resume 返回或await_suspend 由于抛出异常或返回值表示无意暂停协程而没有暂停之后才会被破坏。

    这就是我所做的:

    1 - 为缓冲区类型创建析构函数,如下所示:

    ~const_buffer()
    {
        std::cout << __FUNCSIG__ << std::endl;
    }
    
    ~mutable_buffer()
    {
        std::cout << __FUNCSIG__ << std::endl;
    }
    
    // mutable to const conversion
    
    operator const_buffer() const noexcept { return { data_, size_ }; }
    
    

    2 - 在issue_coro_op 的末尾,从await_suspend 调用我放了类似的打印:

    void issue_coro_op(awaitable& a)
    {
        // use the awaitable to issue the op
        std::cout << "~ " << __FUNCSIG__ << std::endl;
    }
    

    3 - 在await_resume 的末尾我放了一个类似的打印

    4 - 这是传递的缓冲区参数的类型:

    
    awaitable coro_read(const io::mutable_buffer& buff, transfer_flags);
    
    awaitable coro_write(const io::const_buffer& buff, transfer_flags);
    
    template </**/>
    awaitable::awaitable(const io::mutable_buffer& buff) { /*store in ptr*/ }
    
    template </**/>
    awaitale::awaitable(const io::const_buffer& buff) { /*store in ptr*/ }
    
    

    5 - 这是 echo 协程:

    io::task<> run_session(tcp::async_socket sock)
    {
        char buff[1024];
        string server_msg;
        try
        {
            for (;;)
            {
                auto n = co_await sock.coro_read(io::buffer(buff), transfer_flags::unspecified);
                if (buff[n - 1] == '\n')
                    --n;
                cout << ">> " << string_view{ buff, n } << endl;
    
                server_msg = fmt::format("{{ server message : {} }}\n", string_view{ buff, n });
                n = co_await sock.coro_write(io::buffer(server_msg), transfer_flags::unspecified);
            }
        }
        catch (std::exception & ex)
        {
            cout << "[!] a client has disconnected : " << ex.what() << endl;
        }
    }
    

    6 - 用 nc 测试:

    nc localhost 4567
    some message
    { server message : some message }
    

    7 - 服务器输出:

    issue_coro_op -> read
    await_resume -> read
    ~mutable_buffer -> read
    >> some message
    issue_coro_op -> write
    await_resume -> write
    ~const_buffer -> write
    ~mutable_buffer -> write
    

    注意到io::buffer 返回io::mutable_buffer,它在写操作中被转换为io::const_buffer,并且这两个保持有效直到恢复之后它们以相反的顺序被破坏

    我无法使用 clang-cl 8 进行测试,因为它在编译代码时崩溃了!还有 mingw-w64 和 gcc 8,它还不支持协程

    【讨论】:

      猜你喜欢
      • 2021-11-13
      • 2018-07-24
      • 2019-12-08
      • 2013-07-10
      • 1970-01-01
      • 2017-10-17
      • 2016-12-21
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多