Как стать автором
Обновить

Комментарии 16

Спасибо за статью, пример взаимодействия с asio лучше любой синтетики.
<holywar_mode="on"/>
Сколько уже копий сломано об эти корутины, но мне до сих пор не понятно. В бусте уже были нормальные корутины, что мешало внести в язык что-то похожее на boost::context? С чего вдруг предлагаемая модель обёрток над промисами лучше, чем корутины со стеком? Вот теперь придумывают, как использовать asio, хотя он уже работал с бустовыми корутинами.
<holywar_mode="off"/>
Насколько я понял, «модель оберток» предпочли потому, что Boost.Context не переносимый и требует низкоуровневой реализации для каждой платформы. Возможно, играет роль и то, что Coroutines TS предложена Microsoft'ом.
В бусте были потоки пользовательского режима (=волокна), а не сопрограммы.
И я про них же. Хоть они и называются «сопрограммами», но механизм у них как у потоков. Даже в документации написано что один из возможных механизмов реализации, WinFiber, использует волокна из WinAPI.

Да, по поводу преимуществ — тут все просто. Корутину компилятор способен оптимизировать в ноль — а волокно нет.

С do_write тут что-то ужасное сделали. Его нужно было делать нормальной сопрограммой, или вовсе объединить с do_read (единственный смысл разделения этих методов в старом коде я вижу лишь в уменьшении уровня вложенности).
Я намеренно не объединял, чтобы показать проблему с расползанием co_await вверх по стеку. Если сделать do_write «нормальной корутиной», тогда придется убрать co_await do_write() в do_read() и вызывать просто do_write(). В результате новое чтение будет начинаться, не дожидаясь окончания записи. У меня есть такой вариант кода, вечером выложу.
Если сделать do_write «нормальной корутиной», тогда придется убрать ее из-под co_await в do_read()

Нет, не надо. Надо просто дать ей нормальное возвращаемое значение. Например, для примера подойдет std::experimental::future.

Я делал возвращаемое значение std:error_code. Использование std::experimental::future или boost::future — такой подход используется в blogs.msdn.microsoft.com/vcblog/2017/05/19/using-c-coroutines-with-boost-c-libraries, но я хочу этого избежать, потому что тогда будет либо синхронное ожидание (future::wait), либо then(), при использовании которого код уже менее близок к синхронному.

А, извиняюсь, перепутал Concurrency TS и Coroutines TS :-) Точно, из Coroutines TS же конкретные типы сто лет назад убрали...


Можно подключить cppcoro и использовать cppcoro::task


Или можно использовать вот такой тип в качестве возвращаемого значения (пишу без проверки потому что под рукой нет компилятора):


template <typename T = void> class lazy_task { // Для void нужна отдельная специализация
    std::experimental::coroutine_handle<promise_type> handle;

public:
    bool await_ready() { return false; }
    bool await_suspend(std::experimental::coroutine_handle<> coro) {
        handle.resume();
        if (handle.done()) return true;
        handle.promise().m_continuation = coro;
        return false;
    }
    T await_resume() {
        promise_type promise = handle.promise();
        handle.destroy();

        if (promise .m_exception)
            std::rethrow_exception(promise.m_exception);
        else
            return promise.m_value;
    }

    struct promise_type {
        T m_value;
        std::exception_ptr m_exception;
        std::experimental::coroutine_handle<> m_continuation;

        lazy_task get_return_object() { 
           return { std::experimental::coroutine_handle<promise_type>::from_promise(*this) }; 
        }
        std::experimental::suspend_always initial_suspend() { return {}; }
        std::experimental::suspend_always final_suspend() { return {}; }
        void return_value(T value) { 
            m_value = value;
            if (m_continuation) m_continuation.resume();
        }
        void unhandled_exception() { 
            m_exception = std::current_exception(); 
            if (m_continuation) m_continuation.resume();
        }
    };
}

UPD: упс, return_value же вызывается до final suspend point, нельзя в нем m_continuation.resume() вызывать! Вот так надо:


auto final_suspend() { 
    struct awaitable {
        std::experimental::coroutine_handle<> m_continuation;
        bool await_ready() { return false; }
        void await_suspend(std::experimental::coroutine_handle<> coro) {
            if (m_continuation) m_continuation.resume();
        }
        void await_resume() { }
    };
    return awaitable { m_continuation };
}

Чистый мёд!
Спасибо!

Как-то слишком много синтаксического мусора.

В Asio можно намного проще корутины использовать. Неявно.

boost::asio::spawn(io_service, [socket] (boost::asio::yield_context y)) {
    socket.async_read(buffer, y);
    socket.async_write(buffer, y);
});

Это не те корутины которые Coroutines TS.

Конечно не Coroutines TS — их я критикую в своем комменте.
А эти я привел как пример того, что можно все сделать проще, если стоит задача асинхронный код сделать «синхронным» по форме.
Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Истории