0%

Coroutine in C++20

既然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
{ // co_return expr; => promise.return_value(expr); goto final_suspend;
body; // co_return; => promise.return_void(); goto final_suspend;
} // co_yield expr; => co_await promise.yield_value(expr);
catch (...)
{
promise.set_exception(std::current_exception());
}

final_suspend:
co_await promise.final_suspend();
}

對照Pseudo code func和上面範例的countˇ以及promise_tgenerator::promise_type明白了一些東西

– 任何包含co_yieldco_return或者co_await的程式碼都是corutine block,必須定義一個structure,而這個structure必須有個promise_type
– promeise_type需要六個函數, return_valuereturn_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_readyawait_resumeawait_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