【Unity】コルーチン内でサブコルーチンをStartCoroutineを使わず待つ方法【IEnumerator】

はじめに

コルーチン内で更にコルーチンを呼んで終了待ちするシチュエーションはよくあります。
更に StartCoroutine() を呼んで待ちたいところですが、なにかメモリ的に無駄なので少し考えてみました。

結局は IEnumerator インターフェイスですから 直接操作してしまえばいいわけです。

例えばこのような状況

// メインコルーチン
private System.Collections.IEnumerator coMain()
{
	// 何かしらの処理

	// サブコルーチンが終わるのを待つ
//	yield return StartCoroutine( coSub());
	// ↓こう書き換える
	for( var ie = coSub(); ie.MoveNext();) yield return null;

	// 終了
	yield break;
}

// サブコルーチン
private System.Collections.IEnumerator coSub()
{
	// なんか Load とか重い処理
	while( loop)
	{
		yield return null;
	}
	// 終了
	yield break;
}

yield return StartCoroutine( coSub());

の部分を

for( var ie = coSub(); ie.MoveNext();) yield return null;

と書き換え coMain() 自身が coSub() を回して終了待ちしています。

coSub の実行タイミングが多少ずれるかと思いますが、この例ではさほど問題ありません。

応用編

これを利用すると、ミクロなタスクシステムが作れます。
コルーチンの入れ子できるシステムです。

Stack に コルーチンの IEnumerator をPush()し、 自力で回して終了なら Pop() します。

using System;
using System.Linq;
using System.Collections.Generic;
using UnityEngine;

namespace test{

public class MicroTaskTest : MonoBehaviour
{
	private Stack<System.Collections.IEnumerator> m_CoroutineStack = new Stack<System.Collections.IEnumerator>();

	//----------------------------------------------------------------------------
	// コルーチン登録
	private void PushCoroutine( System.Collections.IEnumerator routine) => m_CoroutineStack.Push( routine);
	//----------------------------------------------------------------------------
	// コルーチン終了
	private void PopCoroutine() => m_CoroutineStack.Pop();
	//----------------------------------------------------------------------------
	private void Start()
	{
		// task1 を起動
		PushCoroutine( coTask1() );
	}
	//----------------------------------------------------------------------------
	private void Update()
	{
		// ミクロなタスクシステムを回す
		if( m_CoroutineStack.Count > 0)
		{
			var nowCproutin = m_CoroutineStack.Peek();
			if( !nowCproutin.MoveNext())
			{
				// 終わったらしい
				PopCoroutine();
			}
		}
	}
	//----------------------------------------------------------------------------
	// タスク1
	private System.Collections.IEnumerator coTask1()
	{
		UnityEngine.Debug.Log( $"begin task1");
		for( int i = 0; i < 10; ++i)
		{
			UnityEngine.Debug.Log( $"task1 i={i}");
			yield return null;
		}
		PushCoroutine( coTask2());
		yield return null;
		UnityEngine.Debug.Log( $"end task1");
	}
	//----------------------------------------------------------------------------
	// タスク2
	private System.Collections.IEnumerator coTask2()
	{
		UnityEngine.Debug.Log( $"begin task2");
		for( int i = 0; i < 10; ++i)
		{
			UnityEngine.Debug.Log( $"task2 i={i}");
			PushCoroutine( coTask3());
			yield return null;
		}
		PushCoroutine( coTask4());
		yield return null;
		UnityEngine.Debug.Log( $"end task2");
	}
	//----------------------------------------------------------------------------
	// タスク3
	private System.Collections.IEnumerator coTask3()
	{
		UnityEngine.Debug.Log( $"task3");
		yield break;
	}
	//----------------------------------------------------------------------------
	// タスク4
	private System.Collections.IEnumerator coTask4()
	{
		UnityEngine.Debug.Log( $"task4");
		yield break;
	}
}//class MicroTaskTest
}//namespace test
出力結果
begin task1
task1 i=0
task1 i=1
task1 i=2
task1 i=3
task1 i=4
task1 i=5
task1 i=6
task1 i=7
task1 i=8
task1 i=9
begin task2
task2 i=0
task3
task2 i=1
task3
task2 i=2
task3
task2 i=3
task3
task2 i=4
task3
task2 i=5
task3
task2 i=6
task3
task2 i=7
task3
task2 i=8
task3
task2 i=9
task3
task4
end task2
end task1

task1 の終わりに task2 に飛びます。
task2 は task3 を 10回 呼んで 最後に task4 に飛びます。
後に task2 task1 と終了してます。

このような入れ子構造
task1
{
	task2
	{
		task3
		task3
		task3
		task3
		task3
		task3
		task3
		task3
		task3
		task3
		task4
	}
}

注意としては PushCoroutine() を呼んですぐに yield break すると Stack に積んだ途端 Pop() され意図通り動かないので yield return null; でクッションを挟むと良いです。

Add a Comment

メールアドレスが公開されることはありません。 が付いている欄は必須項目です