Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Question StopCoroutine(), when does it stop?

Discussion in 'Scripting' started by mbiggs2334, Sep 12, 2023.

  1. mbiggs2334

    mbiggs2334

    Joined:
    Mar 31, 2022
    Posts:
    32
    Hello!

    Just a quick question about the internals of stopping a coroutine. Does Stop/StopAll stop the coroutine(s) mid iteration or does the current iteration finish and then it stops.

    I would make the assumption that the current iteration finishes and then it stops, but I'm curious if anyone knows for certain.

    Edit: Just to clarify, by iteration I mean that current frame.
     
  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,563
    If you have to stop your coroutine, you probably should not be using a coroutine.

    Coroutines in a nutshell:

    https://forum.unity.com/threads/des...en-restarting-the-scene.1044247/#post-6758470

    https://forum.unity.com/threads/proper-way-to-stop-coroutine.704210/#post-4707821

    Splitting up larger tasks in coroutines:

    https://forum.unity.com/threads/bes...ion-so-its-non-blocking.1108901/#post-7135529

    Coroutines are NOT always an appropriate solution: know when to use them!

    "Why not simply stop having so many coroutines ffs." - orionsyndrome on Unity3D forums

    https://forum.unity.com/threads/things-to-check-before-starting-a-coroutine.1177055/#post-7538972

    https://forum.unity.com/threads/heartbeat-courutine-fx.1146062/#post-7358312

    Our very own Bunny83 has also provided a Coroutine Crash Course:

    https://discussions.unity.com/t/coroutines-ienumerator-not-working-as-expected/237287/3
     
  3. mbiggs2334

    mbiggs2334

    Joined:
    Mar 31, 2022
    Posts:
    32
    Thank you for the plethora of information Kurt. Helpful as always haha.

    I am curious for your advice then, and I also have a question about one of the posts you linked.

    As for the coroutine portion I'm curious as to what you'd recommend in place of them. I'm currently building what I call (for lack of a better term atm) a meta world simulation. This is where NPCs that aren't in the current scene are having their movement and to some extent their behavior simulated. So, there is going to be a lot of calculations and a lot of nested looping going on with 100s of virtual NPCs.

    It made sense for me to break it up into a coroutine (at least in my novice brain it makes sense). So, the coroutine, or a single simulation step, will run through all the NPCs once. Then once it's run through the current simulation step, the coroutine will be started again. The only time I should have to call StopCoroutine() is when I'm switching scenes.

    Any advice on a better approach? I'm very open to hearing it!

    Also, my question about one of the posts you shared. In one you mentioned coroutines being a bad idea for changing one value to another. Can you elaborate a little more. I'm currently using a coroutine to fade in/out a vignette that appears when the player crouches. It didn't make sense (in my novice brain) to put checks for this in an Update() method for something that will happen somewhat infrequently.
     
    Last edited: Sep 12, 2023
  4. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,563
    Works great until somehow you start two of them at once, eg, crouch / stand very quickly.

    Then it doesn't work so great.

    Let your coroutines die with the objects that were in the previous scene and get unloaded. You don't wanna manage all that crap!

    Coroutines only add value if they are truly fire-and-forget.

    It matters NOTHING where you do those calculations. Coroutines and Update run in precisely the same thread, NEVER at the same time, NEVER "for free."

    Here is some timing diagram help:

    https://docs.unity3d.com/Manual/ExecutionOrder.html
     
  5. mbiggs2334

    mbiggs2334

    Joined:
    Mar 31, 2022
    Posts:
    32
    That's a fair point.

    I understand that they run in the same thread. However, coroutines allow the large number of calculations and nested loops I'll need to run across several frames to reduce the amount of hitching and stutters. So, I very much disagree that it matters nothing where the calculations are placed. If there is a better alternative to coroutines in this specific situation, I'm open to hearing it.
     
  6. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,563
    ANYTHING you can do in a coroutine can also be done simply in Update().

    If there is lots of local bookkeeping related to an iterative task, it MAY be easier to do it in a coroutine.

    If you contemplate stopping / starting that, the benefits begin to weaken.

    That just means you don't grasp how coroutines work. Refer to the above links until you understand why a unit of work done in Update() causes precisely as much CPU load as in a coroutine.

    Coroutines are JUST a language construct, nothing more.

    Repeat after me:

    "Coroutines are simply a cute little tidy state machine, nothing more."

    Put another way and paraphrasing Mike Acton,

    "Coroutines do not run in a magic fairy aether powered by the fevered dreams of CS PhDs."
     
  7. mbiggs2334

    mbiggs2334

    Joined:
    Mar 31, 2022
    Posts:
    32
    Maybe I don't understand, cause what you're saying is confusing to me.

    If I do
    Code (CSharp):
    1. for (int i = 0; i < 1000; i++)
    2. {
    3.     print(i);
    4. }
    in Update(). Unity freezes.

    If I do something like
    Code (CSharp):
    1. for (int i = 0; i < 1000; i++)
    2. {
    3.     print(i);
    4.     if (i % 3 == 0) yield return null;
    5. }
    in a coroutine, it won't freeze.

    I need to be able to do large, nested loops to simulate the things I want to simulate.

    So placing this in Update() doesn't seem like the best way to do it.
     
  8. CodeSmile

    CodeSmile

    Joined:
    Apr 10, 2014
    Posts:
    3,899
    Yes, because for every yield an Update() call (frame) passes.

    The first example does all 1000 iterations in one frame. The result is available immediately.
    The coroutine spreads the same calculation over 333 frames. The result is available only after significant time passed.

    If you call StopCoroutine() in and for the currently running coroutine, the running coroutine will finish executing until the next yield return statement or if it reaches the end of the method.

    If you call StopCoroutine() from elsewhere or stop a different coroutine, there is no currently running coroutine (same thread, see above) and so that coroutine simple does not resume execution.
     
    SisusCo likes this.
  9. mbiggs2334

    mbiggs2334

    Joined:
    Mar 31, 2022
    Posts:
    32
    Which for what I'm currently building is acceptable. As mentioned previously this is to simulate NPCs that aren't even in the current scene, so I won't need immediate access to the data I'm simulating.

    I see, I thought that was the case. Thank you very much for the info!
     
  10. SisusCo

    SisusCo

    Joined:
    Jan 29, 2019
    Posts:
    1,104
    I wouldn't underestimate the benefits that coroutines can have on readability. A lot of boilerplate code gets generated behind the scenes for us when we use them.

    A simple example using a coroutine:
    Code (CSharp):
    1. private void OnEnable() => StartCoroutine(Loop());
    2.  
    3. private IEnumerator Loop()
    4. {
    5.     for(int i = 0; i < 100; i++)
    6.     {
    7.         Step1();
    8.         yield return null;
    9.         Step2();
    10.         yield return null;
    11.         Step3();
    12.         yield return null;
    13.     }
    14. }
    Versus using Update:
    Code (CSharp):
    1. private enum Step
    2. {
    3.     None,
    4.     First,
    5.     Second,
    6.     Third
    7. }
    8.  
    9. private Step step;
    10. private int iteration = 100;
    11.  
    12. private void OnEnable()
    13. {
    14.     step = Step.First;
    15.     iteration = 0;
    16. }
    17.  
    18. private void Update()
    19. {
    20.     if(iteration >= 100)
    21.     {
    22.         return;
    23.     }
    24.  
    25.     switch(step)
    26.     {
    27.         case Step.First:
    28.             Step1();
    29.             step = Step.Second;
    30.             break;
    31.         case Step.Second:
    32.             Step2();
    33.             step = Step.Third;
    34.             break;
    35.         case Step.Third:
    36.             Step3();
    37.             step = Step.None;
    38.             break;
    39.     }
    40.  
    41.     iteration++;
    42. }
    I do recommend adding an extension method like
    void RestartCoroutine(this MonoBehaviour monoBehaviour, IEnumerator coroutineToStart, ref Coroutine startedCoroutine)
    to the project to avoid unintentionally starting multiple instances of the same coroutine running at the same time.
     
  11. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,563
    100% agree. This is what I mean when I wrote:

    But that is not the usual form of coroutines and CERTAINLY not one for this use:

    ESPECIALLY if it is under arbitrary player control and can be multi-started.

    Spiffy! I still won't use a coroutine if I contemplate ad-hoc stoppage.

    I will use a coroutine that can be stopped, but I will ALWAYS do that by passing in a continuation-check delegate, eg, the way the Unity
    WaitUntil()
    method works, accepting an "until" delegate.
     
    SisusCo likes this.
  12. halley

    halley

    Joined:
    Aug 26, 2013
    Posts:
    1,833
    To actually answer your technical question instead of discussing the merits of coroutines...

    If you call it from anywhere "outside" the coroutine, then any code following the last yield which it already ran will not be executed. If you call it from anywhere "inside" the coroutine, then any code up to the next yield will still be run, then no more.
     
    Bunny83 and Kurt-Dekker like this.