Search Unity

Can anyone tell me what is Coroutine technically?

Discussion in 'Editor & General Support' started by pagan_poetry, May 2, 2020.

  1. pagan_poetry

    pagan_poetry

    Joined:
    Feb 15, 2017
    Posts:
    107
    Can anyone tell me what is coroutine technically? :confused: I'm not so deep in C# to understand it by myself.
    Does it exist outside Unity or it's only Unity's feature?
    Does it execute itself in the main single thread each frame or it executes somewhere else during game runtime and do not depend on framerate? Like it has it's own "fixedupdate()"?
    Does Coroutine continue executing while Time.timescale is 0?

    I would be happy if you tell me.
     
  2. PraetorBlue

    PraetorBlue

    Joined:
    Dec 13, 2012
    Posts:
    7,909
    Coroutines are a Unity feature that takes advantage of a C# feature called Iterator Methods, and a C# interface called IEnumerator.

    IEnumerator is a really simple interface that defines just one relevant method and one relevant property for our discussion:
    Code (CSharp):
    1. bool MoveNext();
    2. object Current;
    An IEnumerator is a handle into a collection of objects that provides a nice way to iterate, or enumerate over it. Basically you just call MoveNext() which tells you if the collection has a next item, and if it does, moves Current to point at that new item. So if you have an IEnumerator, you can write code like this to iterate through all elements in the collection:
    Code (CSharp):
    1. IEnumerator theEnumerator;
    2. while(theEnumerator.MoveNext()) {
    3.   var current = theEnumerator.Current;
    4.   // do some stuff
    5. }
    So that's pretty cool right? In fact most common Collection type objects in C# implement the IEnumerable interface (which just has a method to get an IEnumerator), so you can do foreach loops on them. For example List, Queue, HashSet, Array, etc. But wait, there's more! In addition to collection types, C# lets you write methods that act just like collection objects by using the special "yield return" statement. So for example let's say you want to iterate over some numbers you have a couple choices. You could populate a list and iterate over that:
    Code (CSharp):
    1. IEnumerator numbers = new List<int>() {1, 2, 3, 4}.GetEnumerator();
    2. while(numbers.MoveNext()) {
    3.   var current = numbers.Current;
    4.   // do some stuff
    5. }
    6.  
    Or you could use something called an iterator method that kind of acts like a list, but it's actually code!
    Code (CSharp):
    1. IEnumerator GetNumbers() {
    2.   yield return 1;
    3.   yield return 2;
    4.   yield return 3;
    5.   yield return 4;
    6. }
    Then you can treat the return value from that method just like any other IEnumerable collection:
    Code (CSharp):
    1. IEnumerator numbers = GetNumbers();
    2. while(numbers.MoveNext()) {
    3.   var current = numbers.Current;
    4.   // do some stuff
    5. }
    6.  
    Notice that the iterator method returns an IEnumerator, and we can store this thing like a variable and iterate over it.

    Unlike normal methods in C#, when you call an iterator method, it actually doesn't fully execute all the way. In fact it doesn't execute at all until you call MoveNext() on the returned IEnumerator. When you do, it executes up until the first "yield return" statement it finds and the first thing that you "yield return" in the method becomes the Current object on the enumerator. Then every time you call MoveNext(), it continues the execution of the method up until the next yield return method. If the method finishes without seeing a "yield return", MoveNext() returns false, and the iteration is finished. Likewise if it sees a "yield break" statement, MoveNext() returns false and the iteration is finished.

    Ok so that's IEnumerator and iterator methods. So what does that have to do with Coroutines? Well, basically, all Unity does when you call StartCoroutine() is save the IEnumerator from your iterator method and associate it with the GameObject that you started the coroutine from. Then Unity will call MoveNext() in your IEnumerator and looks at the result (which it checks by calling "Current"). If you wrote "yield return null", Unity will say "ok they returned null. I will wait until the next frame to call MoveNext() again on this IEnumerator." If you wrote "yield return new WaitForSeconds(1f);" in your coroutine, Unity sees this and records the current game time. It then checks the current game time every frame until it sees that one second has passed, and when it has, then Unity will call MoveNext() on your coroutine again! This happens as part of the normal game loop. In fact you can see in the manual that Unity will call MoveNext() on coroutines at various points during the game loop depending on what kind of wait object you have yield-returned: https://docs.unity3d.com/Manual/ExecutionOrder.html#Coroutines

    So basically Unity just stores the IEnumerator for your coroutine, and just keeps calling MoveNext(), and looking at Current to decide when to call MoveNext() next.

    That's all there is to it. There's no magic, there's no multithreading. Time.timescale only affects coroutines if you use WaitForSeconds, because Unity uses Time.time to determine how many seconds have passed, and Time.time is affected by Time.timescale.

    More info about IEnumerator here: https://docs.microsoft.com/en-us/dotnet/api/system.collections.ienumerator?view=netcore-3.1
    More info about "yield return" and iterator methods here: https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/keywords/yield
    More info about Coroutines from the Unity manual: https://docs.unity3d.com/Manual/Coroutines.html
     
    Last edited: May 2, 2020