vc2022 で実験。
実験コードはこちら。
https://github.com/ousttrue/co_sample
最小限
とりあえず compile を通して必要最小限を調べる
#include <coroutine>
#include <functional>
#include <iostream>
// Promise
struct Co {
struct promise_type // 👈重要
{
auto get_return_object() {
return Co{std::coroutine_handle<promise_type>::from_promise(*this)}; // 👈重要
}
auto initial_suspend() {
std::cout << "[promise_type] initial_suspend" << std::endl;
return std::suspend_always{};
}
auto final_suspend() noexcept {
std::cout << "[promise_type] final_suspend" << std::endl;
return std::suspend_always{};
}
void unhandled_exception() { std::terminate(); }
void return_void() {}
};
std::coroutine_handle<promise_type> handle; // 👈重要
};
// coroutine
Co run() {
std::cout << "[run] before" << std::endl;
co_return;
std::cout << "[run] after" << std::endl;
}
int main(int argc, char **argv) {
auto co = run();
std::cout << "[main]before resume: " << co.handle.done() << std::endl;
co.handle.resume(); // 👈重要
std::cout << "[main]after resume: " << co.handle.done() << std::endl;
return 0;
}
実行すると
$ ./prefix/bin/simple.exe
[promise_type] initial_suspend
[main]before resume: 0
[run] before
[promise_type] final_suspend
[main]after resume: 1
というふうになります。
resume() で進行する必要あり。
C#
や lua
と同じ感じ。
ビルドに /std:c++latest
が必要。
/std:C++20
でもよいかもしれない。
c++ の coroutine
関数 body で co_return
, co_await
, co_yield
のいずれかを使う関数。
その返り値 R
型は、R::promise_type
型を持っている必要がある。
struct R
{
struct promise_type
{
// 省略
};
};
R run() {
co_return; // co_return などが返り値の R::primise_type を要求する
}
要求を満たしていないと普通に関数がないコンパイルエラーになる。
promise_type
5つの member 関数が必要。
struct R {
struct promise_type {
auto get_return_object();
auto initial_suspend();
auto final_suspend() noexcept;
void unhandled_exception();
void return_void();
};
};
要求を満たしていないと普通に関数がないコンパイルエラーになる。
std::coroutine_handle
コルーチンを再開(resume())したり、コルーチンが終了済み(done())か調べたりする。
R
のメンバーに入れて get_return_object
で初期化するのがパターン。
struct R {
struct promise_type
{
auto get_return_object() {
return R{
.handle = std::coroutine_handle<promise_type>::from_promise(*this)
};
}
// 他は省略
};
std::coroutine_handle<promise_type> handle; // 👈 これ
};
R のメンバーにしないと resume() する手段が無い?
await (suspend) してみよう
#include <coroutine>
#include <functional>
#include <iostream>
#include <thread>
struct Awaiter
{
bool await_ready()
{
std::cout << "[Awaiter] await_ready" << std::endl;
// false ならば suspend する。
return false;
}
void await_suspend(std::coroutine_handle<> handle)
{
std::cout << "[Awaiter] await_suspend" << std::endl;
}
void await_resume() { std::cout << "[Awaiter] await_resume" << std::endl; }
};
// Promise
struct Co
{
struct promise_type
{
auto get_return_object()
{
return Co{ std::coroutine_handle<promise_type>::from_promise(*this) };
}
auto initial_suspend()
{
std::cout << "[promise_type] initial_suspend" << std::endl;
return std::suspend_always{};
}
auto final_suspend() noexcept
{
std::cout << "[promise_type] final_suspend" << std::endl;
return std::suspend_always{};
}
void unhandled_exception() { std::terminate(); }
void return_void()
{
std::cout << "[promise_type] return_void" << std::endl;
}
};
std::coroutine_handle<promise_type> handle;
};
// coroutine
Co
run()
{
std::cout << "[run] before" << std::endl;
co_await Awaiter{};
std::cout << "[run] after" << std::endl;
// co_return;
}
int
main(int argc, char** argv)
{
auto co = run();
for (int i = 0; !co.handle.done(); ++i) {
std::cout << "[main]resume " << i << std::endl;
co.handle.resume();
}
return 0;
}
[promise_type] initial_suspend
[main]resume 0
[run] before
[Awaiter] await_ready
[Awaiter] await_suspend
[main]resume 1
[Awaiter] await_resume
[run] after
[promise_type] return_void
[promise_type] final_suspend
Awaiter をユーザーから隠して間接的に生成させる
上記の例では下記のように直接 Awaiter を作りました。
co_await Awaiter{};
本来の使い方では、Awaiter を間接的に生成して隠蔽するデザインになっているようです。
2通りの手段があります。
- Awaiter operator co_await(const T &)
- Awaiter promise_type::await_transform(const T &)
Awaiter と Awaitable の違いが不明瞭。
operator co_await が Awaiter で、
await_transform が Awaitable と言っているようにみえた。
時間から Awaiter を生成する例。
co_await 10ms;
// Awaiter に変換する
Awaiter operator co_await(const chrono::milliseconds &)
// もしくは
Awaiter promise_type::await_transform(const chrono::milliseconds &)
auto initial_suspend() {
std::cout << "[promise_type] initial_suspend" << std::endl;
return std::suspend_always{}; // 👈 awaiter
}
auto final_suspend() noexcept {
std::cout << "[promise_type] final_suspend" << std::endl;
return std::suspend_always{}; // 👈 awaiter
}
https://cpprefjp.github.io/reference/coroutine/suspend_always.html
co_yield: 中断(suspend)するときに coroutine から呼び出し側に値を返す
#include <coroutine>
#include <functional>
#include <iostream>
#include <thread>
struct Awaiter
{
bool await_ready()
{
std::cout << "[Awaiter] await_ready" << std::endl;
return false;
}
void await_suspend(std::coroutine_handle<> handle)
{
std::cout << "[Awaiter] await_suspend" << std::endl;
}
void await_resume() { std::cout << "[Awaiter] await_resume" << std::endl; }
};
// Promise
struct Co
{
struct promise_type
{
auto get_return_object()
{
return Co{ std::coroutine_handle<promise_type>::from_promise(*this) };
}
auto initial_suspend()
{
std::cout << "[promise_type] initial_suspend" << std::endl;
return std::suspend_always{};
}
auto final_suspend() noexcept
{
std::cout << "[promise_type] final_suspend" << std::endl;
return std::suspend_always{};
}
void unhandled_exception() { std::terminate(); }
void return_void()
{
std::cout << "[promise_type] return_void" << std::endl;
}
int m_value = -1;
#if 1
std::suspend_always yield_value(int value) // 👈 co_yield 時に呼ばれる
{
m_value = value;
return {};
}
#else
std::suspend_always await_transform(int value) // co_await で書けばこう?
{
m_value = value;
return {};
}
#endif
};
std::coroutine_handle<promise_type> handle;
};
// coroutine
Co
run()
{
std::cout << "[run] before" << std::endl;
co_yield 1;
co_yield 2;
co_yield 3;
std::cout << "[run] after" << std::endl;
// co_return;
}
int
main(int argc, char** argv)
{
auto co = run();
for (int i = 0; !co.handle.done(); ++i) {
std::cout << "[main]resume " << i << "=>" << co.handle.promise().m_value // 👈 m_value が更新される
<< std::endl;
co.handle.resume();
}
return 0;
}
[promise_type] initial_suspend
[main]resume 0=>-1
[run] before
[main]resume 1=>1
[main]resume 2=>2
[main]resume 3=>3
[run] after
[promise_type] return_void
[promise_type] final_suspend
co_return で値を返す
#include <coroutine>
#include <functional>
#include <iostream>
#include <thread>
// Promise
template<typename T>
struct Co
{
struct promise_type
{
auto get_return_object()
{
return Co{ std::coroutine_handle<promise_type>::from_promise(*this) };
}
auto initial_suspend()
{
std::cout << "[promise_type] initial_suspend" << std::endl;
return std::suspend_always{};
}
auto final_suspend() noexcept
{
std::cout << "[promise_type] final_suspend" << std::endl;
return std::suspend_always{};
}
void unhandled_exception() { std::terminate(); }
T m_value = -1;
void return_value(T value)
{
m_value = value;
std::cout << "[promise_type] return_value" << std::endl;
}
};
std::coroutine_handle<promise_type> handle;
T Value()
{
return handle.promise().m_value;
}
};
// coroutine
Co<int>
run()
{
std::cout << "[run] before" << std::endl;
co_return 345;
std::cout << "[run] after" << std::endl;
}
int
main(int argc, char** argv)
{
auto co = run();
std::cout << "[main]before resume: " << co.handle.done() << " => "
<< co.Value() << std::endl;
co.handle.resume();
std::cout << "[main]after resume: " << co.handle.done() << " => "
<< co.Value() << std::endl;
return 0;
}
[promise_type] initial_suspend
[main]before resume: 0 => -1
[run] before
[promise_type] return_value
[promise_type] final_suspend
[main]after resume: 1 => 345
再開(resume)するとき coroutine に値を渡す
Task ぽくするには、suspend 時に thread に処理を投げて、
thread 終了時に結果を resume に投入して再開する必要がありそう。
#include <chrono>
#include <coroutine>
#include <functional>
#include <future>
#include <iostream>
#include <thread>
// Promise
template<typename T>
struct Co
{
struct promise_type
{
auto get_return_object()
{
return Co{ std::coroutine_handle<promise_type>::from_promise(*this) };
}
auto initial_suspend()
{
std::cout << "[promise_type] initial_suspend" << std::endl;
return std::suspend_always{};
}
auto final_suspend() noexcept
{
std::cout << "[promise_type] final_suspend" << std::endl;
return std::suspend_always{};
}
void unhandled_exception() { std::terminate(); }
auto await_transform(const std::function<T()>& work)
{
struct awaiter
{
std::future<T> m_future;
std::thread m_t;
bool await_ready() { return false; }
void await_suspend(std::coroutine_handle<>) {}
T await_resume() // 👈 await_resume の返り値が co_await の左辺値になる
{
std::cout << "thread block ? tid#" << std::this_thread::get_id()
<< std::endl;
return m_future.get();
}
};
std::promise<T> p;
std::future<T> f = p.get_future();
std::thread t([work, p = std::move(p)]() mutable {
std::cout << "[work]tid#" << std::this_thread::get_id() << std::endl;
T value = work(); // 👈 thread 上で work を実行
p.set_value(value); // 結果を返す
});
// join しないのでエラーにならなように
t.detach();
return awaiter{
std::move(f),
std::move(t),
};
}
T m_value = -1;
void return_value(T value)
{
m_value = value;
std::cout << "[promise_type] return_value" << std::endl;
}
};
std::coroutine_handle<promise_type> handle;
T Value() { return handle.promise().m_value; }
};
// coroutine
Co<int>
run()
{
std::cout << "[run] before" << std::endl;
int result = co_await []() {
std::this_thread::sleep_for(std::chrono::seconds(2));
return 678;
};
std::cout << "[run] after" << std::endl;
co_return result;
}
int
main(int argc, char** argv)
{
auto co = run();
while (!co.handle.done()) {
co.handle.resume();
}
std::cout << "[main]" << co.Value() << std::endl;
return 0;
}
[promise_type] initial_suspend
[run] before
thread block ? tid#24008
[work]tid#9152
[run] after
[promise_type] return_value
[promise_type] final_suspend
[main]678
実用するには、工夫して promise.set_value が完了してから resume してあげるようにする必要あり。
boost asio の coroutine api を使う
asio の記事に移動しました。
参考
https://qiita.com/tyanmahou/items/522ea1c592db3468940c
https://www.scs.stanford.edu/~dm/blog/c++-coroutines.html
https://downloads.ctfassets.net/oxjq45e8ilak/4kVoTkxYBiVM9lPBZiG2HO/a5e36dc80fd898885269bd6320c96196/Pavel_Novikov_Uchimsya_gotovit_C_korutiny_na_praktike_2020_06_28_18_49_49.pdf