@Ryiah , nope, Unity starts the coroutine. Coroutines also have nothing to do with C#, itâs a Unity-specific feature.
Thereâs a very obscure difference.
When you do this one:
yield return PrintNumber(i)
Unity starts the inner coroutine after you have yielded.
If you do this one:
yield return StartCoroutine(PrintNumber(i))
the inner coroutine is started before the yield.
The only time I could find where this has an impact is if you extract the IEnumerator/Coroutine, and then try to do something with it in the inner coroutine:
public class TestClass : MonoBehaviour {
private Coroutine coroutine;
private void Start() {
StartCoroutine(PrintNumbers());
}
private IEnumerator PrintNumbers() {
for (int i = 0; i < 3; i++) {
coroutine = StartCoroutine(PrintNumber(i));
yield return coroutine;
}
}
private IEnumerator PrintNumber(int x) {
if (something) {
StopCoroutine(coroutine);
}
Debug.Log(x);
yield return null;
}
}
Now, if âsomethingâ is true on the first iteration of the loop, the coroutine variable hasnât been set yet, and youâd get a nullref. On later frames, youâd get a more omnious bug, where youâd be stopping the last coroutine. If youâd been using the yield return IEnumerator instead, the StopCoroutine call wouldâve done absolutely nothing instead.This means taht the coroutine wouldâve kept running after the StopCoroutine.
Why youâd ever stop a coroutine from inside itself with StopCoroutine instead of yield break is⌠Idk. But this is the difference.
Youâre still wrong, though, the first yield there definitely starts a coroutine, as itâs yielding an IEnumerator inside a coroutine. Unity does that behind your back. Unless youâre only responding to the thread title?
@Baste thx a lot for the explanation, I do have one more question:
Can I start multiple coroutines at the same time and still know they have all ended?
AFAIK:
If I want to know they have all ended, I need yield return on sub-coroutine.
If I want to start multiple coroutines (without waiting), I canât use yield return, I have to do ugly callbacks to know they have all ended properly.
Is there a yield that can work on an array of IEnumerator/Coroutines?
You could build a custom yieldinstruction to do the job. Youâll have to do the ugly callbacks, but you can do them in the custom instruction rather than in your class. Youâll probably have to send in this as an argument to have a target to run all of the inner coroutines on.
Also, remember that you can use params in the constructor to be able to take an arbitrary amount of arguments.
You can start the Coroutines up without yielding them similar to Basteâs first post, that makes it fairly easy to make your own WaitForAll helper method
public class NewBehaviourScript : MonoBehaviour
{
IEnumerator Start()
{
print("Starting " + Time.time);
var routine1 = StartCoroutine(Enumerator1());
var routine2 = StartCoroutine(Enumerator2());
var routine3 = StartCoroutine(Enumerator3());
yield return MyHelper.WaitForAll(routine1, routine2, routine3);
print("Done " + Time.time);
}
}
//in another .cs file
public static class MyHelper
{
public static IEnumerator WaitForAll(params Coroutine[] coroutines)
{
for (int i = 0; i < coroutines.Length; i++)
{
yield return coroutines[i];
}
}
}
I do not believe so. I have seen some weird infinite loops where a yield instruction didnât result in a frame jump. Iâd have to test it out to prove it though.
I would actually handle waiting for multiple coroutines to finish differently. Iâd probably give each one a simple lambda call back. Or pass in a variable for them to update.
However this is starting to get a little complex for coroutines. Coroutines excel at simple time tasks. But tread lightly when things get complex.
I did a check, and youâre right. I realize why I thought what I did, though:
Strangeness
using System.Collections;
using UnityEngine;
public class TEST2 : MonoBehaviour {
private void Start() {
StartCoroutine(TestSkipWait());
StartCoroutine(TestWaitShorter());
}
private IEnumerator TestSkipWait() {
var dontWait = StartCoroutine(WaitIf(false));
yield return new WaitForSeconds(1f);
Debug.Log("don't wait before: " + Time.frameCount); //prints 56
yield return dontWait;
Debug.Log("don't wait after: " + Time.frameCount); //prints 57
}
private IEnumerator TestWaitShorter() {
var waitForHalf = StartCoroutine(WaitFor(.5f));
yield return new WaitForSeconds(1f);
Debug.Log("wait shorter before: " + Time.frameCount); //prints 56
yield return waitForHalf;
Debug.Log("wait shorter after: " + Time.frameCount); //prints 56
}
private IEnumerator WaitFor(float time) {
yield return new WaitForSeconds(time);
}
private IEnumerator WaitIf(bool b) {
if (b) {
yield return new WaitForSeconds(1f);
}
}
}
tl;dr: if you yield a coroutine thatâs hit and passed all itâs internal yields, you immediately return. If you yield a coroutine that never hit a yield due to condition flow, it will wait one frame.
The way I see it, I would classify that has a bug: an IEnumerator that never hits a yield is an empty set. An empty set transposed to couroutine would mean âwait for no-frameâ, thatâs is âdonât waitâ. So the coroutine should end immediately and not wait for 1 frame.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class IEnumeratorTest : MonoBehaviour
{
// Use this for initialization
void Start ()
{
//var enumerator = SomeEnumerator(true);
var enumerator = SomeEnumerator(false); // false will generate an empty set, therefore no iteration occurs.
while (enumerator.MoveNext())
Debug.Log("Ieration");
}
IEnumerator SomeEnumerator(bool someCondition)
{
if (someCondition)
{
yield return 0;
yield return 1;
}
}
}
I think its intentional. It seldom ever comes up in practice. The only time I ever hit it was doing a WaitForEndOfFrame inside a LateUpdate. Or some similar shenanigans. Because it was already the end of the frame it didnât yield at all, and looped forever.
Do note, to @Ryiah 's benefit, it wasnât always this way. It used to be that you HAD to call StartCoroutine. But Unity than later added it where they auto started it.
If you go to any older tutorials though and see mention of having to call it, I could easily see thinking itâs necessary, because many people would have said it was necessary.
Yielding an IEnumerator was added in 5.3. and this is not documented anywhere. I was having some issues porting some assets to older versions and this was kinda confusing.