Coroutines were introduced in C++20 standard. Unfortunately, the implementation isn't such friendly even to experienced developers because the standard library provides only interfaces and leaves the event loop, awaiters, promises etc. on programmers.
This is a minimal working example which could help people to start learning:
#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));
}
}
The magic happens here co_await sleep{10};
. The current coroutine suspends and the awaiter takes its handler to resume it later. In this example, the awaiter defers a task into an event loop to checks if the sleep time expires.
This is a simple event loop with a queue of tasks:
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));
}
Play with this example a bit before starting to read about the coroutines in detail. Hope, it was helpful!
Top comments (2)
Due to
std::suspend_always final_suspend()
coroutines frames are never destroyed. This is memory leak :) godbolt.org/z/K7n8GKhjK Vs godbolt.org/z/r87M5qof4Thank you for pointing this. You're right -stackoverflow.com/questions/760395... . For the simplest example, it is better to use
std::suspend_never
of course.