Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Field-referenced Coroutine can only be started once?

Discussion in 'Scripting' started by HeaDiii, May 15, 2018.

  1. HeaDiii

    HeaDiii

    Joined:
    May 18, 2015
    Posts:
    61
    Hey there,

    I do have some Coroutines that need to be executed quite often and sometimes need to be stopped during execution. The only way that stopping works for me is to put this coroutine into a IEnumerator field so i can start and stop it with the reference.

    Now I discovered, that those referenced coroutines only start exactly once. If I start these coroutines directly I don't seem to have any problems. I'm using Unity 2017.3.1f1

    To make sure its not only in my project, I also tested it with this simple code. Am I doing something wrong or is this a bug?

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class Test : MonoBehaviour
    6. {
    7.     private IEnumerator someCoroutine;
    8.     void Start ()
    9.     {
    10.         someCoroutine = MyRoutine();
    11.     }
    12.    
    13.     void Update () {
    14.  
    15.         if (Input.GetKeyDown(KeyCode.Space))
    16.         {
    17.             StartCoroutine(someCoroutine); // <- Only works exactly once
    18.             // StartCoroutine(MyRoutine()); // <- Works as often as I want.
    19.         }
    20.     }
    21.  
    22.     IEnumerator MyRoutine()
    23.     {
    24.         Debug.Log("i was started");
    25.         yield return null;
    26.     }
    27. }
    28.  
     
  2. Owen-Reynolds

    Owen-Reynolds

    Joined:
    Feb 15, 2012
    Posts:
    1,992
    This is from memory and I always confuse the "IEnumerator" handle with the "Coroutine" handle, but:

    Your [someCoroutine] is a link to one particular instance of that coroutine function. You can start and stop it once. If you want to run MyRoutine again, create another with someCoroutine=MyRoutine();. A way to see this is that there are ()'s. You're calling MyRoutine and getting a return value of a runnable coroutine object. It's StartCoroutine(MyRoutine()) spread over two steps.

    The point of this is being able to have handles to multiple runs of the same coroutine. You can have catAction=MyRoutine(cat); dogAction=MyRoutine(dog); and so on. You can run them, check them, stop them independently. If you don't need that, don't use it. StartCoroutine(MyRoutine()) works fine if you'll never run two copies of it at the same time.
     
    AlanMattano and HeaDiii like this.
  3. Brathnann

    Brathnann

    Joined:
    Aug 12, 2014
    Posts:
    7,186
    Try this version

    Code (CSharp):
    1.     private Coroutine someCoroutine;
    2.  
    3.     void Update()
    4.     {
    5.  
    6.         if (Input.GetKeyDown(KeyCode.Space))
    7.         {
    8.             someCoroutine = StartCoroutine(MyRoutine()); // <- Only works exactly once
    9.             // StartCoroutine(MyRoutine()); // <- Works as often as I want.
    10.         }
    11.     }
    12.  
    13.     IEnumerator MyRoutine()
    14.     {
    15.         Debug.Log("i was started");
    16.         yield return null;
    17.     }
    18.  
    19.  
    You could also try more effective coroutines on the asset store.
     
    HeaDiii likes this.
  4. HeaDiii

    HeaDiii

    Joined:
    May 18, 2015
    Posts:
    61
    Thanks for the explanation, now I know how this works :)

    I need those handles since I eventually need to stop my coroutines during execution, even if there's always just one copy running at the same time.

    I changed my code to this and it's working as expected:
    Code (CSharp):
    1.     void Update () {
    2.  
    3.         if (Input.GetKeyDown(KeyCode.Space))
    4.         {
    5.             someCoroutine = MyRoutine();
    6.             StartCoroutine(someCoroutine);
    7.             //StartCoroutine(MyRoutine());
    8.         }
    9.     }
     
  5. Owen-Reynolds

    Owen-Reynolds

    Joined:
    Feb 15, 2012
    Posts:
    1,992
    Well, StopCoroutine("MyCoroutine"); works fine to stop the only copy (it stops all copies.) In the old days we had to do that, with a global bool to check whether it was running at all. In fact, we could hackily run and stop multiple copies at once: each coroutine needed a horrible rewrite to check it's own personal global done flag after each yield, quitting it true.

    But this new way is standard computer science. If it makes sense, definitely use it. I had small problems using the other method (which Brathnann wrote,) with a Coroutine type. But I was handling coroutine chains running over multiple gameObjects, so who knows. It's probably just as good for simple stuff.
     
  6. AlanMattano

    AlanMattano

    Joined:
    Aug 22, 2013
    Posts:
    1,501
    Hei @Owen-Reynolds that was very interesting! Can you put it in clean code?

    @HeaDiii For debugging I use to put a lot of console messages to let me know what I'm calling. For example, how many times Start is called.

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. public class Test : MonoBehaviour
    5. {
    6.     private IEnumerator someCoroutine;
    7.  
    8.     void Start ()
    9.     {
    10.         Debug.Log("Starting...\n Calling my rotine");
    11.         someCoroutine = MyRoutine();
    12.     }
    13.  
    14.  
    15.     void Update ()
    16.    {
    17.         if (Input.GetKeyDown(KeyCode.Space))
    18.         {
    19.             Debug.Log("SpaceBar was press...\n Calling someCoroutine");
    20.             StartCoroutine(someCoroutine); // <- Only works exactly once
    21.         }
    22.     }
    23.  
    24.  
    25.     IEnumerator MyRoutine()
    26.     {
    27.         Debug.Log("MyRoutine is running \n");
    28.         yield return null;
    29.         Debug.Log("MyRoutine is over \n");
    30.     }
    31. }
     
    Last edited: May 21, 2018
  7. Owen-Reynolds

    Owen-Reynolds

    Joined:
    Feb 15, 2012
    Posts:
    1,992
    Huh. The example in the docs is a little hidden: it's in StopCoroutine. That man entry was updated for the new "get a handle" system, while the rest weren't. I think I remember reading it when directed by "new" patch notes.