First of all, coroutines fundamentally run independent from each other. When you call StartCoroutine Unity registers a new coroutine which will take it’s place in some internal list. It you “yield” on another Coroutine, that coroutine is basically on hold until the “nested” coroutine finishes. It’s possible that they implemented the continuation with some sort of callback which gets invoked inside the coroutine scheduler when the nested coroutine finishes. A coroutine is finished when MoveNext returns false.
However stopping the coroutine completely breaks this mechanism since the coroutine is simply kicked out of the internal list. So the “outer” coroutine still waits for it’s signal to continue which will never be send.
As i said in a comment on the other question, stopping a coroutine should in general be avoided. While it’s a lot saver than terminating a thread from the outside (since a coroutine can only be stopped at yields) it’s still a bit unpredictable in which state the coroutine is at the moment. You should implement the termination into the nested coroutine and let it finish gracefully (i.e. yield break;)
One option would be to run the nested coroutine “inline” without actually starting a new coroutine.
bool terminateNested = false;
public IEnumerator CoMain()
{
Debug.Log("Start Main");
terminateNested = false;
IEnumerator nested = CoLoop(); // no StartCoroutine!!!
while(!terminateNested && nested.MoveNext())
yield return nested.Current;
Debug.Log("End Main");
}
Here we iterate the IEnumerator manually. We simply yield the yielded value of the nested coroutine, so it has the same effect. You can simply set “terminateNested” to “true” and the nested coroutine will “terminate”. However the last yield will still complete. So if the nested coroutine yields a “WaitForSeconds(1000)” CoMain can not continue until that wait is done.
Another solution is to build a wrapper around the IEnumerator of the nested coroutine and provide a seperate way of stopping the coroutine. The wrapper would simply return another controlling coroutine which you actually yield on in your CoMain. When we receive the terminate signal we stop the “wrapped” coroutine and let the controlling coroutine terminate gracefully.
Something like that:
public class StoppableCoroutine
{
bool terminated = false;
IEnumerator payload;
Coroutine nested;
MonoBehaviour mb;
public StoppableCoroutine(MonoBehaviour mb, IEnumerator aCoroutine)
{
payload = aCoroutine;
nested = mb.StartCoroutine(wrapper());
this.mb = mb;
}
public Coroutine WaitFor()
{
return mb.StartCoroutine(wait());
}
public void Stop()
{
terminated = true;
mb.StopCoroutine(nested);
}
private IEnumerator wrapper()
{
while (payload.MoveNext())
yield return payload.Current;
terminated = true;
}
private IEnumerator wait()
{
while(!terminated)
yield return null;
}
}
public statoc class MonoBehaviourExtension
{
public static StoppableCoroutine StartCoroutineEx(this MonoBehaviour mb, IEnumerator coroutine)
{
return new StoppableCoroutine(mb, coroutine);
}
}
With that class and extension method you can simply do this:
StoppableCoroutine _coLoop;
public IEnumerator CoMain()
{
Debug.Log("Start Main");
_coLoop = StartCoroutineEx(CoLoop());
yield return _coLoop.WaitFor();
Debug.Log("End Main");
}
To actually stop the nested coroutine you would simply call
_coLoop.Stop();
This approach has several advantages. Now you can have multiple coroutines to wait for a single one which isn’t possible with the Coroutine object. You can only yield a Coroutine object once. Since our WaitFor method creates a new coroutine each time you call it that problem doesn’t exist.
Finally a link to my CoroutineHelper class which does something similar.
Firstly, + 1 for a well-written question :) I think this is expected behaviour. CoMain()'s execution is paused until CoLoop() yields, and since CoLoop is stopped, this never happens. So CoMain() enters an infinite state of waiting for a yield that isn't going to come. CoLoop doesn't yield arbitrarily at the point it is stopped, and I can't find any documentation to suggest why this should happen. Rather than "solve" this problem (which while being an excellent, clear example, is somewhat artificial), what's the actual scenario in the game you're trying to achieve?
– tanoshimiRelated question: http://answers.unity3d.com/questions/324469/coroutine-does-not-stop-when-having-a-coroutine-in.html
– Bunny83