Простейший пример coroutines в C++20

369
Простейший пример coroutines в C++20
Простейший пример coroutines в C++20

Coroutines были введены в стандарт C++20. К сожалению, его реализация не так удобна даже для опытных разработчиков, поскольку стандартная библиотека предоставляет только интерфейсы, а цикл событий, ожидание, обещания и т.д. оставляет на усмотрение программистов.

Это простой рабочий пример, который поможет вам начать обучение:

#include <iostream>
#include <coroutine>
#include <thread>
#include <queue>
#include <functional>

std::queue<std::function<bool()>> task_queue;

struct sleep {
    sleep(int n) : delay{n} {}

    constexpr bool await_ready() const noexcept { return false; }

    void await_suspend(std::coroutine_handle<> h) const noexcept {
        auto start = std::chrono::steady_clock::now();
        task_queue.push([start, h, d = delay] {
            if (decltype(start)::clock::now() - start > d) {
                h.resume();
                return true;
            } else {
                return false;
            }
        });
    }

    void await_resume() const noexcept {}

    std::chrono::milliseconds delay;
};


struct Task {
    struct promise_type {
        promise_type() = default;
        Task get_return_object() { return {}; }
        std::suspend_never initial_suspend() { return {}; } 
        std::suspend_always final_suspend() noexcept { return {}; }
        void unhandled_exception() {}
    };
};

Task foo1() noexcept {
    std::cout << "1. hello from foo1" << std::endl;
    for (int i = 0; i < 10; ++i) {
        co_await sleep{10};
        std::cout << "2. hello from foo1" << std::endl;
    }
}

Task foo2() noexcept {
    std::cout << "1. hello from foo2" << std::endl;
    for (int i = 0; i < 10; ++i) {
        co_await sleep{10};
        std::cout << "2. hello from foo2" << std::endl;
    }
}

//call foo
int main() {
    foo1();
    foo2();

    while (!task_queue.empty()) {
        auto task = task_queue.front();
        if (!task()) {
            task_queue.push(task);
        }
        task_queue.pop();

        std::this_thread::sleep_for(std::chrono::milliseconds(10));
    }
}

Волшебство происходит здесь co_await sleep{10};. Текущая корутина приостанавливается, а awaiter принимает ее обработчик, чтобы возобновить ее позже. В этом примере awaiter откладывает задачу в цикл событий, чтобы проверить, не истекло ли время ожидания.

Это простой цикл событий с очередью задач:

while (!task_queue.empty()) {
    auto task = task_queue.front();
    if (!task()) {
        task_queue.push(task);
    }
    task_queue.pop();
    std::this_thread::sleep_for(std::chrono::milliseconds(10));
}

Поиграйте немного с этим примером, прежде чем приступить к подробному изучению Coroutines (корутинов). Надеюсь, это было полезно!

Возможно вам так же будет интересно:

Подборка бесплатных книг по программированию от O’Reilly