コルーチンについて


あまり理解せずにコルーチンを使用していたので、もう少しちゃんと理解しようと見直してみた。
そもそもコルーチンとは。。。
以下Wikipediaより。

コルーチン(英: co-routine)とはプログラミングの構造の一種。サブルーチンがエントリーからリターンまでを一つの処理単位とするのに対し、コルーチンはいったん処理を中断した後、続きから処理を再開できる。接頭辞 co は協調を意味するが、複数のコルーチンが中断・継続により協調動作を行うことによる。
サブルーチンと異なり、状態管理を意識せずに行えるため、協調的処理、イテレータ、無限リスト、パイプなど、継続状況を持つプログラムが容易に記述できる。
コルーチンはサブルーチンを一般化したものと考えられる。コルーチンをサポートする言語には Modula-2、Simula、Icon、Lua、C#、Limbo などがある。マルチスレッドで原理的には同じことができるため、現在はそちらが使われるケースが多い。

ということで、処理を中断し再び呼び出すと続きから再開できるということのようです。

C#ではIEnumeratorという型があってこれでコルーチンを実現できる。Enumerator(数え上げ)というように、Iteratorのような感じで実装がされていて、リファレンスを見てみるとMoveNext()とReset()というメソッドがある。MoveNext()の戻り値はboolで、次の数え上げが可能かどうかが返ってくる。

処理を中断するには yield というキーワードで、
yield return 0;
と書くと処理を中断できる。
return の後ろはIEnumerator型インスタンスの現在の値としたいものを返すと保持される。
yield break;
と書くとそれ以上の数え上げを中止できる。

これを組み合わせてボタンを押すたびにコルーチンの処理を呼び出すサンプルはこちら。

IEnumerator coroutine;

void Awake() {
	this.coroutine = Routine();
}

void OnGUI() {
	if (GUI.Button(new Rect(Screen.width / 2 - 80, Screen.height / 2 - 15, 160, 30), "test")) {
		bool hasNext = this.coroutine.MoveNext();

		if (!hasNext) {
			// this.coroutine.Reset(); // NotSupportedExceptionが出る
			this.coroutine = Routine();
			print("Reset");
		}
		else {
			print("current : " + this.coroutine.Current);
		}
	}
}

IEnumerator Routine() {
	print("Coroutine-1");
	yield return 1;
	print("Coroutine-2");
	yield return 2;
	print("Coroutine-3");
	yield return 3;
}

/*
Coroutine-1
current : 1
Coroutine-2
current : 2
Coroutine-3
current : 3
Reset
以下繰り返し
*/

数え上げはもちろんなんだけど、これを使うと状態管理なんかをしなくともユーザーのインタラクションとともに遷移するようなものも作れそう。ちなみにIEnumerator.Reset()は実装されていないとNotSupportedExceptionとなり呼び出せない。

便利なコルーチンだけど、Unityでは重たい処理をフレームごとに分けて実行できるようにStartCoroutine(IEnumerator routine)というメソッドが用意されている。
StartCoroutineで指定した処理はyield returnされる度、毎フレーム再度呼び出しがされて処理が終わるまで続く。
StartCoroutine()の戻り値はCoroutine型で、こいつの親クラスはYieldInstructionという、yieldに使われる戻り値を返してくれる。StartCoroutine()を呼ぶ際に yield return StartCoroutine() としてやるとコルーチンで指定した処理が終わった後に処理が再開される。
通常IEnumeratorは数え上げの機能で、yield return **で返した値は先のサンプルのように現在の値として保存される。それをUnityではYieldInstruction型(WaitForSeconds, WaitForFixedUpdate, Coroutineなど)をyield returnさせると、渡されたコルーチンの処理を終えたあと呼び出し元のメソッドを呼び出して処理を再開させるようにしてある。(という解釈でいいのでしょうか??)

/// <summary>
/// Awake.
/// </summary>
IEnumerator Start() {
	print("Start start");
	StartCoroutine(this.Method("A")); // 処理を待たない
	print("yield");
	yield return StartCoroutine(this.Method("B")); // 処理を待つ
	print("Start end");
}

/// <summary>
/// サンプルメソッド。
/// </summary>
IEnumerator Method(string param) {
	print("Method start " + param);
	yield return new WaitForSeconds(1f);
	print("Method end " + param);
}

/*
結果
Start start
Method start A
yield
Method start B
Method end A
Method end B
Start end
*/

コルーチンはあくまでメインスレッドで実行されているので本当の意味で違うスレッドで動かすにはThreadクラスを使う必要があるようだけど、Threadを使う処理にはUnityのAPIは使えないらしい。

参考)
Wisdomsoft | 処理の分割(コルーチン)
安定の漢になるために | コルーチンについて

コメントを残す