Search Unity

Do coroutines first execute immediately like a function call?

Discussion in 'Scripting' started by Gordon_G, Jun 23, 2020.

  1. Gordon_G

    Gordon_G

    Joined:
    Jun 4, 2013
    Posts:
    372
    Hey all, trying to resolve some conflicting information: when I start a coroutine, for example,

     StartCoroutine( myCoroutine);


    does myCoroutine start executing immediately? - That is, irrespective of any yield return statements you might have within, the first execution is like a function call? Or does it wait until the Update event loop has been completed before starting it's first run?

    Thanks for your help!
     
  2. Where did you get conflicting information?

    Yes. And I advise you to read the actual documentation. No ambiguity there.
    https://docs.unity3d.com/Manual/Coroutines.html
     
    Bunny83, Joe-Censored and Gordon_G like this.
  3. Gordon_G

    Gordon_G

    Joined:
    Jun 4, 2013
    Posts:
    372
    Yes. And I advise you to read the actual documentation. No ambiguity there

    I read the documentation and It doesn't say that explicitly, that's why I asked.
     
  4. Owen-Reynolds

    Owen-Reynolds

    Joined:
    Feb 15, 2012
    Posts:
    1,998
    If you search more on "unity coroutine execute immediately" you'll probably find examples of where the coroutine starts right away (up the the 1st yield). https://answers.unity.com/questions/784469/question-about-coroutine-execution-order.html has a demo (in the comments in the 2nd answer). But, there's no Unity rule about it. There's no reason the next version of Unity won't flip it around, or it works differently on Android vs. PC. Computers have lots of rules like this where you can't assume the exact way it works. But it rarely matters. The only time I've cared is when the start of a coroutine might set paused to true and I need to be sure it's set _right now_, not at the end of the frame, since I check it later in Update.

    But many coroutines eventually get a normal function running them, making it not matter when coroutines run. You call beginFade(), it does some checking, sets variables right away 100% guaranteed, then runs the _beginFade() coroutine. It saves your main code having to write StartCoroutine everywhere.
     
  5. But where did you get the idea that it doesn't start to execute right away? Where this misinformation is coming from? Because if you see a method call in virtually any programming language the default is to execute right away. It is not something we developers usually explicitly write down because this is a kind of force of nature.
    If it is execute right away for some reason and it is not implied, then we need to communicate it, but it's also very rare.
     
  6. Owen-Reynolds

    Owen-Reynolds

    Joined:
    Feb 15, 2012
    Posts:
    1,998
    Coroutines emulate threads, and threads generally start whenever. Maybe right away, maybe much later, maybe half of each. All that usually matters with a thread is they let you know when they're done. Of course, coroutines are only useful because they guarantee some timing, but it still seems natural to assume anything not precisely specified is up for grabs. For example, coroutines A and B run every frame and A was started first. That doesn't mean A runs before B, unless the manual specifically says so.

    This is really ingrained once you're forced to find race conditions for a pair of threads (I think a Junior level class).
     
    VincentiusIV and PraetorBlue like this.
  7. PraetorBlue

    PraetorBlue

    Joined:
    Dec 13, 2012
    Posts:
    7,909
    I think this is a reasonable confusion to have. It's kind of the like the vagaries of when Awake/OnEnable/Start run. If you look at https://docs.unity3d.com/Manual/ExecutionOrder.html you might assume that at the beginning of the scene, ALL Awake methods run, followed by ALL OnEnable methods. I just had a bug recently in one of my projects where I made that assumption and I learned that Unity apparently calls Awake() then OnEnable() on each object before moving onto the next one. Likewise, https://docs.unity3d.com/ScriptReference/Object.Instantiate.html says something about Awake and OnEnable, but it doesn't say anything about Start() and Update(). Will they run in this frame? Next frame? It's not immediately clear. I think similar ambiguity is present on https://docs.unity3d.com/Manual/Coroutines.html
     
  8. a436t4ataf

    a436t4ataf

    Joined:
    May 19, 2013
    Posts:
    1,933
    I don't see that in the documentation - just re-read it twice and it never seems to say what you claim.

    There's a lot of situations where Unity appears to delay coroutines starting, by one frame. Some of them are due to the non-defined way Unity orders courtine execution (As mentioned in the docs! There is no guarantee what order coroutines will execute in).

    Others appear to be due to the way Unity handles nested coroutine invocations - I've seen cases going back many years where Unity inserts an artificial one-frame delay and we can't find any code-level reason why, and end up writing code in all other classes that does a "wait for end of frame" in *every* class just to let Unity catch-up with what it was supposed to do in the first place :).

    There are also a lot of threads about people running into 1-frame delays with coroutines.

    So, overall, I'd say: there's plenty of reason to be confused and asking about this.
     
  9. PraetorBlue

    PraetorBlue

    Joined:
    Dec 13, 2012
    Posts:
    7,909
    It says this in the docs:
    That reads pretty clearly to me. By the time StartCoroutine returns, all the code in the coroutine will have run up to the first yield.
     
    Last edited: Oct 29, 2020
    Bunny83 and Joe-Censored like this.
  10. Madgvox

    Madgvox

    Joined:
    Apr 13, 2014
    Posts:
    1,317
    If you are starting a Coroutine via string (
    StartCoroutine( "MyCo" )
    ), then all bets are off and Unity can do whatever it wants. Executing Coroutines via string are not recommended regardless, so I won't consider that case.

    If you're starting a Coroutine via a function call, then understanding C# syntax will make it pretty clear that a coroutine is executed immediately -- and not by Unity, but by your own code!

    Code (CSharp):
    1. // Any normal method call is executed immediately and returns
    2. int Add ( int x, int y ) {
    3.   return x + y;
    4. }
    5.  
    6. int result = Add( 1, 2 );
    7. print( result ); // --> 3
    8.  
    9. // A method call passed to another method must be executed
    10. // and return before the second method can be executed
    11. int PrintInt ( int val ) {
    12.   print( val );
    13. }
    14.  
    15. PrintInt( Add( 1, 2 ) ); // --> 3
    16.  
    17. // This state of affairs is identical to starting a
    18. // coroutine. StartCoroutine takes an IEnumerator as an argument
    19. // and MyCo can't give it one until it gets to a yield return and
    20. // returns.
    21. IEnumerator MyCo () {
    22.   // this will get executed immediately
    23.   yield return new WaitForSeconds( 2 );
    24.   // this will get executed later
    25. }
    26.  
    27. // like so:
    28. StartCoroutine( MyCo() );
    29.  
    30. // which is semantically identical to:
    31. IEnumerator result = MyCo();
    32. StartCoroutine( result );
    So to answer your initial question:

    A coroutine is not like a function call, it is exactly a function call!


    See @PraetorBlue's post below. I am completely and totally wrong here!
     
    Last edited: Oct 30, 2020
    Bunny83 and kopf like this.
  11. PraetorBlue

    PraetorBlue

    Joined:
    Dec 13, 2012
    Posts:
    7,909
    I'm not sure I agree with this.

    If you have an iterator function like this:
    Code (CSharp):
    1. IEnumerator Test() {
    2.   Debug.Log("1");
    3.   yield return null;
    4.   Debug.Log("2");
    5. }
    Then simply calling Test() like this will NOT cause "1" to print:
    Code (CSharp):
    1. IEnumerator t = Test();
    It's actually still an implementation detail (albeit a documented one that you can rely on) that Unity will execute the iterator up to the first yield. To manually do the equivalent you would have to write this:
    Code (CSharp):
    1. IEnumerator t = Test();
    2. bool hasMore = t.MoveNext();
    The MoveNext() call is what actually triggers the code before the first yield to run. As mentioned above though, Unity does do this for coroutines and it is documented.
     
    Bunny83, kopf and Madgvox like this.
  12. Madgvox

    Madgvox

    Joined:
    Apr 13, 2014
    Posts:
    1,317
    @PraetorBlue It appears that you have caught me with my pants down! I had always assumed that enumerated functions were essentially normal methods that yielded, akin to async methods. I guess C# does more syntax magic than I gave it credit for, and since my experience with C# ends at the unity environment, I've never had cause to use enumerators otherwise.

    Thanks for teaching me something new. I rescind my previous post! :D
     
    Bunny83 and PraetorBlue like this.
  13. a436t4ataf

    a436t4ataf

    Joined:
    May 19, 2013
    Posts:
    1,933
    Ok, thanks. I read that a couple of times and didn't pick up that interpretation, but I see what you mean.

    It's not well-written documentation. I *think* I know in all cases, but it requries some effort. When, for instance, does StartCoroutine "return" in e.g. "yield return StartCoroutine( something )" <-- in this case: I believe it returns when you expect: i.e. not instantly (!) but delayed until the lazy-evaluation of whatever is triggering the yield-return block, at which time I believe it returns instantly.

    ...but hey: there's plenty here to be confusing :). The lack of docs on what Courtine really is, how its implemented, what contract exists for executing IEnumerators, how MoveNext and Current will be treated, etc ... really doesn't help. It's a shame that something so useful is so buried by Unity.

    (you can read the MS docs, and build up encyclopaedic knowlege of iterators, which isn't easy given how dense but also obtuse the MS docs are here, and how much is omitted even there! - but it's sad that Unity doesn't just document it themselves)
     
  14. PraetorBlue

    PraetorBlue

    Joined:
    Dec 13, 2012
    Posts:
    7,909
    It must return instantly. You have a statement:
    Code (CSharp):
    1. yield return StartCoroutine(MyCoroutine());
    This is really just:
    Code (CSharp):
    1. yield return <some expression>;
    In order to yield, the runtime must fully evaluate whatever that <some expression> is. In this case, it's the return value of a call to
    StartCoroutine()
    , which we established above will run that coroutine up until the first yield statement before returning.

    I'd be happy to be proven wrong here, but I think the C# runtime would have to be doing something very strange, such as not evaluating the yielded expression until IEnumerator.Current is called, which seems unlikely.

    Note this may be different from what happens in this case:
    Code (CSharp):
    1. yield return MyCoroutine();
    Here we are returning a raw IEnumerator. In this case it is again up to the Unity coroutine implementation when MoveNext gets invoked on that IEnumerator. There may very well be a frame delay there, it's probably worth testing, and that particular behavior I believe is under-documented.
     
    Last edited: Oct 30, 2020
    a436t4ataf and kopf like this.
  15. mgstauff

    mgstauff

    Joined:
    Sep 6, 2017
    Posts:
    59

    For anyone like myself who'd like to read the docs directly about this question, the above quote is from the Scripting docs page on MonoBehavior.StartCoroutine, not from the first document link in this thread that goes to the Manual page on Coroutines.

    Here's the relevant link: https://docs.unity3d.com/ScriptReference/MonoBehaviour.StartCoroutine.html