[unsolved]Nested coroutine won't return from yield when StopCoroutine called

TL;DR:
I have a line:

  • yield return someMonoBehaviourScript.StartCoroutine(name, parameter);
    If I call:
  • someMonoBehaviourScript.StopCoroutine(name);
    The coroutine “name” stops properly, but the yield never returns

Long version:
Consider the two scripts at the bottom of this post.

Human_Behaviour.cs is added to a GameObject

The problem is that StartSingleYieldingCoroutine never returns from the:
“yield return callingScript.StartCoroutine(name, parameter);”
line. BUT Test() coroutine is properly STOPPED and RESTARTED.
Functionally, it’s not a problem, but I guess every call to StartSingleYieldingCoroutine will leave a junk entry in the stack while the application is running.

What I’m expecting should happen:

  1. Update calls StartSingleCoroutine
  2. StartSingleCoroutine starts StartSingleYieldingCoroutine (INSTANCE 1)
  3. StartSingleYieldingCoroutine (INSTANCE 1) starts Test
  4. ---- Next frame ----
  5. Update calls StartSingleCoroutine
  6. StartSingleCoroutine starts StartSingleYieldingCoroutine (INSTANCE 2)
  7. StartSingleYieldingCoroutine (INSTANCE 2) stops Test
  8. StartSingleYieldingCoroutine (INSTANCE 1) naturally returns from “yield return callingScript.StartCoroutine” since Test is now stopped. <<< THIS NEVER HAPPENS
  9. StartSingleYieldingCoroutine (INSTANCE 2) starts Test
    etc…

As additional information, if I remove the “callingScript.StopCoroutine(name);” line from StartSingleYieldingCoroutine, the “yield return callingScript.StartCoroutine…” returns properly.

Please, keep in mind the posted code is just a very simplified version of what I’m trying to do, to make the problem I’m facing as evident as possible. I KNOW that if I were trying to just accomplish restarting Test() on every frame, there are easier ways to do so.
CoroutineHandler.cs is a handler that makes using coroutines easier, and I’m trying to make this work in this particular way since it will help simplify this classes’ use.

Human_Behaviour.cs

using UnityEngine;
using System;
using System.Collections;
using System.Collections.Generic;

public class Human_Behaviour : MonoBehaviour {
    CoroutineHandler coroutineHandler = new CoroutineHandler();
   
    void Awake(){
        coroutineHandler.Initialize(this);
    }
   
    void Update () {
        coroutineHandler.StartSingleCoroutine("Test", null, true);
    }
   
    int debugTest = 0;
    int debugStartRun = 0;
    IEnumerator Test(){
        debugStartRun++;
        yield return new WaitForSeconds(5F);
        ++debugTest;
    }
}

CoroutineHandler.cs

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class CoroutineHandler {
    MonoBehaviour callingScript;

    public void Initialize(MonoBehaviour script){
        callingScript = script;
    }

    public void StartSingleCoroutine(string name, object parameter = null, bool stopOtherInstance = false){
        // Take advantage of the calling script's MonoBehaviour to run StartSingleYieldingCoroutine
        callingScript.StartCoroutine(StartSingleYieldingCoroutine(name, parameter, stopOtherInstance));
    }

    public IEnumerator StartSingleYieldingCoroutine(string name, object parameter = null, bool stopOtherInstance = false){
        callingScript.StopCoroutine(name);
         yield return callingScript.StartCoroutine(name, parameter);
        debug.Log("NEVER GETS HERE!!!");
    }
}

Hello,

You should avoid executing named coroutine in that case. In 4.6 they added a StopCoroutine(Coroutine) function that should be nicer to use. (Use a Coroutine object rather than a string).

Executing a coroutine in the Update function doesn’t seems the best idea. Update is launched each frame, that mean you try to exec/Stop a coroutine each frame. Your coroutine can’t finish, it wait for 5 sec while a frame is 1/30 sec.

For your test i will suggest you to add keyInput in ur update, in order to exec your coroutine statement only when a key is pressed.

1 Like

Woha! I did not know that, thanks! I’m still on 4.5.

This is just for testing purposes, and that is exactly the intended behavior. If the coroutine were to end, I’d have known StopCoroutine wasn’t working.

I’d still like to see a way to have this working in 4.5 without having to resort to upgrading to have access to the new StopCoroutine. I’ve read some people have seen performance degrade in the new version.

You can try with that extended coRoutine classes.
http://twistedoakstudios.com/blog/Post83_coroutines-more-than-you-want-to-know

StartCoroutine(“myfunction”); works well when “myfunction” is in the same script. In your case, i think the function is “not found”

Thanks for pointing me to this.

Definitely not the case. Each StartCoroutine is called inside the MonoBehaviour (callingScript) where the coroutines are located. As I mentioned, the coroutine does run properly (as evidenced by the “debugStartRun” variable changing).

Y’know you can have multiple versions of Unity on your computer, so you can test performance yourself pretty easily. I haven’t noticed any performance issues that’d make me give up uGUI and various other nifty upgrades.

Thanks! I’ll give it a try.

Personally I wrote my own implementation for Coroutines (that works in tandem with unity Coroutines), that gives cancel/pause capabilities, as well as implementing your own custom yield instructions.

https://code.google.com/p/spacepuppy-unity-framework/source/browse/trunk/SpacepuppyBase/RadicalCoroutine.cs

Here’s an article I wrote about it.

http://jupiterlighthousestudio.com/unity-radical-coroutines/

Sorry I didn’t respond sooner, I’m not getting e-mail notifications for some reason.

As I mentioned, this is part of a Coroutine handling class that is already working great in my project, with the upside that it has almost no impact on garbage or performance. The problem itself is the result of me trying to make the class completely seamless; as it is, I have to call a method at the end of every Coroutine to inform the class that is has ended naturally… no bueno.

Your solution looks quite powerful.

Ever solved your initial Problem? i got the same now: http://forum.unity3d.com/threads/how-to-use-the-new-stopcoroutine-ienumerator.249732/#post-2153523

Do i have to go with a custom implementation? Seems odd, like a bug, that the yield never returns.

Nope; unfortunately, I found that if you StopCoroutine on a Coroutine that is being yielded, it’ll keep yielding indefinitely.

I’ve found no workaround either, so it’s one thing I keep my eyes peeled for. I can’t say how it fares on newer Unity versions, since I’m still on 4.5.

ok, thanks, i did a small workaround, not as capable as my initial idea but breakable from the outside. But you can only break/transit to another State once per frame.

Dumbed down example if anyone is interested:

using UnityEngine;
using System.Collections;

public class wasd : MonoBehaviour {
  
public enum State {    Approach,BackOff,Charge,MeleeCombat,RangedCombat,Focus,Jumping,Melee,Patrol,Staggered,Unalarmed,Commanded,Dead,None }
bool statebreak = false;

private    IEnumerator FSM(){      
    while (true){
        switch (state) {              
            case State.Approach:    yield return StartCoroutine( StateApproach()    );break;                  
            case State.BackOff:        yield return StartCoroutine( StateBackOff()        );break;                              
            case State.Charge:        yield return StartCoroutine( StateCharge()        );break;              
            case State.MeleeCombat:    yield return StartCoroutine( StateMeleeCombat()    );break;              
            case State.Melee:        yield return StartCoroutine( StateMelee()        );break;                  
            case State.Patrol:        yield return StartCoroutine( StatePatrol()        );break;
            case State.Staggered:    yield return StartCoroutine( StateStaggered()    );break;              
            case State.Unalarmed:    yield return StartCoroutine( StateUnalarmed()    );break;              
            case State.Commanded:    yield return StartCoroutine( StateCommanded()    );break;                  
            case State.Dead:        yield return StartCoroutine( StateDead()        );yield break;    break;
            default:                yield return null;                                break;
        }
    }
}

private    IEnumerator StatePatrol(){
  
    //coroutine local variables here
  
    while(true){      
      
        //stuff happens here, also state Transition are declared here
      
      
        //End block of any State
        yield return new WaitForEndOfFrame();                //end of current frame, all Coroutines and Update()/LateUpdate() calls that may set stateBreak have finished by now
        if(stateBreak){    stateBreak = false; yield break; }    //break State, the new "state" is already set by the function that has set "stateBreak to true"
        yield return null;                                    //continue after next Update()          
    }
}

public    void Hit(int damage){
    health -= damage;  
          
    state = State.Staggered;
    stateBreak = true;    //current state will be broken at the end of this Frame, next State will be running after Update() of the next frame      
}

//Info, Unity Timing: 1.Update - 2.Coroutines - 3.LateUpdate, then WaitForEndOfFrame();
  
}

I usually don’t promote my own Asset Store packages. In this case, it seems to be fit perfectly, especially because I can’t see another direct solution.

I was having some comparable issues and created a custom coroutine solution to overcome this and other issues. You can find the link in my signature.