はかせのラボ

私の頭の中を書いていく雑記ブログです

C++ タスクシステム④ ~非同期コルーチン~

あいさつ

どうも、はかせです。
今回はコルーチンの非同期版を作ってみたいと思います。

Coroutine traits

前回も紹介したコルーチンとして扱うために必要なものです。
非同期版でもコルーチンはコルーチンなので必要です。

ただ前回と違う点があります。

・initial_suspend()の戻り値をstd::experimental::suspend_neverにする
awaitできるコルーチンの実行状態は基本的にawait実装に委ねます。
なので最初から実行状態にしておくのがベターみたいです。

//最初から実行状態にしておく
std::experimental::suspend_never initial_suspend() { return {}; }

・std::experimental::suspend_never return_valueを実装する
yieldを使用する予定がないのでreturn_valueで値を取得します。

std::experimental::suspend_never return_value(T value)
{
	mValue.set_value(value);
	return {};
}

前回各項目の最後のほうでちょろっと言っていたやつです。

・エラーハンドリング
非同期ということでエラーが出たら怖いのでハンドリングします。
エラーハンドリングはunhandled_exception()で行います。

//ハンドリングされていないエラーが出たら
void unhandled_exception()
{
	//プログラムを異常終了させる
	std::terminate();
}

Awaiter

await可能な型であることを示すために必要なものです。
正式にこの名前であるわけではないようですが色んな記事でこの名前を使っていたので私もこの名前でやってみようと思います。

await_ready()

非同期処理が実行済みかを返すメソッドです。
実行済みであればtrueを返します。

bool await_ready() const
{
	return mFuture.wait_for(std::chrono::seconds(0)) == std::future_status::ready;
}

await_suspend()

非同期処理を一時中断します。
再開の仕込みも行います。

void await_suspend(HandleType h)
{
	std::thread([this, h]()
	{
		mFuture.wait();
		h.resume();
	}).detach();
}

await_resume()

resume()が呼ばれたとき呼ばれます。
非同期処理の値を返します。

T await_resume()
{
	return mFuture.get();
}

await_transform()

任意の型をawait可能にしたい場合、この関数を実装します。
実装内容はテンプレートで型を受け取って上記の3つの関数を実装するだけです。

template<typename T>
auto await_transform(std::future<T> f)
{
	struct Awaiter
	{
		std::future<T> mFuture;

		bool await_ready() const
		{
			return mFuture.wait_for(std::chrono::seconds(0)) == std::future_status::ready;
		}

		void await_suspend(HandleType h)
		{
			std::thread([this, h]()
			{
				mFuture.wait();
				h.resume();
			}).detach();
		}

		T await_resume()
		{
			return mFuture.get();
		}
	};

	return Awaiter{ std::move(f) };
}

実行結果

それでは実行してみます。
今回は次のコルーチンを非同期実行してみます。

MyCoroutineAsync<int> TestAsyncs::MyCoroutineTestAsync()
{
	auto n = co_await std::async(std::launch::async, []() -> int
	{
		std::this_thread::sleep_for(std::chrono::seconds(1));
		return 10;
	});

	return n;
}

1秒まって10を返すだけのコルーチンです。
以下のように使ってみます。

/*使用*/
int main(int argc, char const* argv[])
{
	getchar();
	auto test = new TestAsyncs();
	auto myTestAsync1 = test->MyCoroutineTestAsync();
	auto myTestAsync2 = test->MyCoroutineTestAsync();
	cout << "MyCoroutineTestAsync1:" << myTestAsync1.GetReturnValue() << endl;
	cout << "MyCoroutineTestAsync2:" << myTestAsync2.GetReturnValue() << endl;
	auto myTestAsync3 = test->MyCoroutineTestAsync();
	cout << "MyCoroutineTestAsync3:" << myTestAsync3.GetReturnValue() << endl;
	getchar();
}

では実行結果です。
f:id:hakase0274:20181221233327g:plain

最初の2つは同じタイミングで非同期に1秒待っているので
同じタイミングで出ています。

対して最後の1つは最初の2つが終わってから1秒待っているので
少し間があります。

あとがき

今回は非同期コルーチンでした。
ほぼほぼ前回と変わらず実装できました。

ちなみにただ非同期をやりたいだけならstd::futureとか
std::threadとか既に出来合いのものがあるのでそちらを使うと楽です。
今回私は学習目的であることと自前実装できた方が出来ることの幅が広がると思い今回の勉強をしました。

それでは今回はこの辺でノシ

参考

qiita.com