Временные значения, переданные функции, которая возвращает ожидаемое, остаются действительными после точки приостановки с помощью co_await - PullRequest
2 голосов
/ 18 марта 2020

Я добавляю поддержку сопрограмм ts в классе сокетов asyn c на основе портов завершения windows io. Без сопрограмм ввод-вывод может быть выполнен следующим образом:

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

или

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

, где каждый из них возвращает void и уведомляет результат через обработчик и не требует кеширования параметры, потому что операция будет передана по возвращении из функции

Но с сопрограммами функция вернет ожидаемое, что после ожидания с 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 };
}

копия в первом случае не наносит вреда, а во втором - причиняет вред, поскольку она вызывает распределение кучи и копирует векторное содержимое.

Так что я искал решение этой проблемы, и, читая эту страницу сопрограммы ts я наткнулся на это:

Типичный генератор yield_value будет хранить (копировать / перемещать или просто сохранить адрес, так как время жизни аргумента пересекает точку приостановки внутри co_await) его аргумент int o объект генератора и возвращение 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, также будет оставаться действительным до тех пор, пока сопрограмма не будет возобновлена ​​или разрушен, это правда? или это специально для yield_value в виде обещания?

1 Ответ

0 голосов
/ 18 марта 2020

Протестировано с 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 - и это сопрограмма эха:

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 - протестировано с n c:

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 с g cc 8, который пока не поддерживает сопрограммы

...