既然C++20把Coroutine列為標準配備之後,必須試著了解玩法
從最簡單的範例開始
The simplest coroutine example
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
| #include <iostream> #include <experimental/coroutine>
#define CORETURN_ENABLE
struct generator { struct promise_type { int current_value; auto initial_suspend() { return std::experimental::suspend_always{}; } auto final_suspend() { return std::experimental::suspend_always{}; } auto get_return_object() { return generator{handle_type::from_promise(*this)}; } void unhandled_exception() { std::exit(1); } auto yield_value(int v) { current_value = v; return std::experimental::suspend_always{}; } #ifdef CORETURN_ENABLE void return_value(int v) { current_value = v; } #else void return_void() {} #endif }; bool move_next() { coro.resume(); return !coro.done(); } int current_value() { return coro.promise().current_value; } using handle_type = std::experimental::coroutine_handle<promise_type>; handle_type coro; }; generator count() { co_yield 42; co_yield 56; #ifdef CORETURN_ENABLE co_return 999; #endif }
int main() { auto g = count(); while (g.move_next()) std::cout << g.current_value() << std::endl; #ifdef CORETURN_ENABLE std::cout << g.current_value() << std::endl; #endif return 0; }
|
分析
假設我們有一個Coroutine的程式碼片段,例如
1 2 3 4 5
| template <typename TRet, typename … TArgs> TRet func(TArgs args…) { body; }
|
邊義氣會幫我們做類似這樣的處理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| template <typename TRet, typename ... TArgs> TRet func(TArgs args...) { using promise_t = typename coroutine_traits<TRet, TArgs...>::promise_type;
promise_t promise; auto __return__ = promise.get_return_object();
co_await promise.initial_suspend();
try { body; } catch (...) { promise.set_exception(std::current_exception()); }
final_suspend: co_await promise.final_suspend(); }
|
對照Pseudo code func
和上面範例的count
ˇ以及promise_t
和generator::promise_type
明白了一些東西
– 任何包含co_yield
,co_return
或者co_await
的程式碼都是corutine block
,必須定義一個structure,而這個structure必須有個promise_type
– promeise_type需要六個函數, return_value
和return_void
不可同時存在
– 中間狀態必須保存在promise_type中
– 當co_yield
交出控制權之後,必須使用coro.resume()
恢復執行
以下討論另外一個關鍵字 co_await
co_await
先從古早時期的Callback說起,給出一個範例
From a callback start
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| #include <thread> #include <iostream> using call_back = std::function<void(int)>; void Add100ByCallback(int init, call_back f) { std::thread t([init, f]() { std::this_thread::sleep_for(std::chrono::seconds(1)); f(init + 100); }); t.detach(); } int main() { Add100ByCallback(10, [](int v) { std::cout << v << "\n"; }); std::this_thread::sleep_for(std::chrono::seconds(5)); }
|
程式本身沒有什麼意義,只是模擬長時間處理的情形
Awaitable object
Coroutine版根據上面的版本修改,新增了一些東西
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| struct Task { struct promise_type { auto get_return_object() { return Task{}; } auto initial_suspend() { return std::experimental::suspend_never{}; } auto final_suspend() { return std::experimental::suspend_never{}; } void unhandled_exception() { std::terminate(); } void return_void() {} }; };
struct Add100AWaitable { Add100AWaitable(int init) :init_(init) {} bool await_ready() const { return false; } int await_resume() { return result_; } void await_suspend(std::experimental::coroutine_handle<> handle) { auto f = [handle, this](int value) mutable { result_ = value; handle.resume(); }; Add100ByCallback(init_, f); } int init_; int result_; };
Task Add100ByCoroutine(int init, call_back f) { std::cout << "Before co_await: " <<std::this_thread::get_id() << "\n"; int ret = co_await Add100AWaitable(init); std::cout << "After co_await: " << std::this_thread::get_id() << "\n"; f(ret); }
|
上面的Task不需要多做介紹,Add100ByCoroutine
也不需要多說些什麼
直接看Add100AWaitable
的部分
同樣的Add100AWaitable
也有必須要注意的點
– 中間值一樣存在 Awaitable object
中
– 必須有await_ready
,await_resume
和await_suspend
的存在
– 以上面的例子來看,當Callback完成之後,我們手動讓Coroutine繼續執行上去
– 呼叫Add100ByCoroutine和corutime resume的Thread不一定是同一個,需要把狀態保留到awaitable object中
Reference
How C++ coroutines work
Coroutine Theory
From Algorithms to Coroutines in C++
Coroutines and Reference Parameters