Difference between "yield return IEnumerator" and "yield return StartCoroutine"

Given this context:

using UnityEngine;
using System.Collections;

public class TestClass : MonoBehaviour {
    void Start () {
        StartCoroutine(PrintNumbers());
    }

    IEnumerator PrintNumbers () {
        for (int i = 0; i < 10; i++) {
            //yield return PrintNumber(i);
            //yield return StartCoroutine(PrintNumber(i));
        }
    }

    IEnumerator PrintNumber (int x) {
        Debug.Log(x);
        yield return null;
    }
}

The former is simply a function call. You aren’t starting a coroutine in C# unless you call StartCoroutine().

@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.

13 Likes

I was attemtping to point out that it’s required to call StartCoroutine() in C#. UnityScript doesn’t require it.

Right, C# in Unity. Gotcha.

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?

No. I wasn’t aware it starts one behind the scenes. I’m trying to find a more in-depth entry for how it works now.

@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’re looking for something like:

yield return WaitForAll(IEnumerator1(), IEnumerator2(), IEnumerator3());

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.

Thx, I am thinking along this line as well. (I was hoping an easy helper class exists somewhere already haha.)

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 think every yield waits at least one frame. So if routine 1 and 2 stops before routine 3, your WaitForAll might wait two frames too long.

I’ve post a solution to a problem similar to yours, here:

https://forum.unity3d.com/threads/sequental-workflow-coroutine.448956/#post-2905905

But in your case, instead of in sequence, you want to execute a set of tasks in parallel. So the BT script would be:

parallel
    Task1
    Task2
    Task3

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.

Is that a bug? It’s a bit strange, at least.

1 Like

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.

Case in point they still haven’t changed the documentation (as of 1/19/2017)
https://docs.unity3d.com/ScriptReference/MonoBehaviour.StartCoroutine.html

Or rather I should say, they haven’t cleaned up what is now vagueness in the documentation.

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.

It’s a bit crazy I hit this exact weird behavior a year later.

So I have a list of event handlers that return IEnumerator, which I yield return in a loop, like following:

            for (var i = 0; i < handlers.Count; i++) {
                handler = handlers[i] as IEventHandler<T>;
                yield return handler.HandleAsync(message, timer, callback);
                Debug.Log(Time.frameCount);
            }

I really want handler.HandleAsync to yield as soon as possible, so I use conditional “yield break” in these handlers.

But it ends up creating the same problem as @Baste 's conditional non-yield example, all yields cost me a frame.

So:

  • Is there a good solution to ask Unity to yield immediately (aka continue in the same frame), since conditional yield break doesn’t work?

  • Is yield return new WaitForSeconds(0) a good idea?

I’m pretty sure yielding a WaitForSeconds(0) is the same as a yield return null. You might check.

You could handle your issue by having HandleAsync have an out bool parameter indicating if you should even yield:

handler = handlers[i] as IEventHandler<T>;
bool shouldYield;
var yieldTask = handler.HandleAsync(message, timer, callback, out shouldYield);
if(shouldYield)
    yield return yieldTask;

I don’t know if there’s any other way to achieve this due to the issue discussed upthread.