C++20 Coroutine
可暫停函數
從最簡單的開始,假如我們希望下面這個東西可以跑起來
cpp
/* WHAT? */ coFoo() {
std::cout << 1 << std::endl;
// return to caller
std::cout << 2 << std::endl;
}
- 呼叫 coFoo
- 沒暫停
- 印出 1
- 暫停,等待 resume
- 印出 2
- 沒暫停,直接結束
我們要先準備一個 coFoo return 的東西:
cpp
#include <coroutine>
struct Dummy {
// 一個用來讓 caller 和 callee 溝通的橋樑
struct promise_type {
~promise_type() {
std::cerr << "~promise()" << std::endl;
}
Dummy get_return_object() {
return Dummy(
std::coroutine_handle<promise_type>::from_promise(*this));
}
auto initial_suspend() noexcept {
// 進入時不暫停
return std::suspend_never{};
}
auto final_suspend() noexcept {
// 結束時不暫停
return std::suspend_never{};
}
void unhandled_exception() {}
};
explicit Dummy(const std::coroutine_handle<promise_type>& handle)
: handle_(handle) {}
std::coroutine_handle<promise_type> handle_;
};
然後就可以這樣寫:
cpp
Dummy coFoo() {
std::cout << 1 << std::endl;
co_await std::suspend_always{};
std::cout << 2 << std::endl;
}
int main() {
std::cout << "start" << std::endl;
auto g = coFoo();
// 印出 1 ,因為我們 initial_suspend 回傳的是 suspend_never
std::cout << "main" << std::endl;
// handle_ 是和 coroutine 互動的橋樑,呼叫 resume 就是 resume 該 coroutine。
g.handle_.resume();
// 印出 2
// 印出 ~promise() ,因為我們 initial_suspend 回傳的是 suspend_never
std::cout << "main" << std::endl;
std::cout << "exit" << std::endl;
return 0;
}
理解 Promise
他的機制可以直接這樣理解:
cpp
co_await promise.initial_suspend();
try {
<body-statements>
} catch (...) {
promise.unhandled_exception();
}
co_await promise.final_suspend();
中間收割(yield)一些值
現在換成一個有不斷 yield 的函數:
cpp
Generator foo() {
co_yield 1;
co_yield 2;
co_yield 3;
}
我們在 Promise 裡面就需要多支援 yield_value:
cpp
int val_;
auto yield_value(int v) noexcept {
val_ = v;
return std::suspend_always{};
}
在外面要操作 Coroutine 就可以使用這三條:
handle_.done()
: 回傳 coroutine 是否結束handle_.resume()
: 回去 coroutine 繼續跑handle_.promise().val_
: 取得暫存在 Promise 裡面 yield 到的數值。
cpp
class Generator {
public:
struct Promise;
using promise_type = Promise;
using Handle = std::coroutine_handle<Promise>;
struct Promise {
int val_;
Generator get_return_object() {
return Generator(Handle::from_promise(*this));
}
auto initial_suspend() noexcept {
return std::suspend_never{};
}
auto final_suspend() noexcept {
return std::suspend_always{};
}
auto yield_value(int v) noexcept {
val_ = v;
return std::suspend_always{};
}
void unhandled_exception() {
}
};
explicit Generator(const Handle& handle): handle_(handle) {
}
~Generator() {
if (handle_) {
handle_.destroy();
}
}
int get() const {
return handle_.promise().val_;
}
bool done() const {
return handle_.done();
}
void next() {
if (!handle_.done()) {
handle_();
}
}
private:
Handle handle_;
};