Coroutine with reference not stopping - Missed the out keyword

Using Unity 2019.1.9f1

I just fell into this problem with a simple Coroutine manager I wrote, the problem was strangling me so i decided to write an example to be sure the problem was not my fault. Or else I made a wrong assumption on how the Coroutine system is meant to work.


The problem is:

  • I start a coroutine and save it reference
  • I stop the coroutine and start a new one using the same reference by changing it’s target IEnumerator
  • !!! The coroutine actually won’t stop, the new one starts (on the same reference?)

Here is the code, it’s simple, a phone is ringing, the user by pressing D (running on script 2) invokes an unity event, the telephone (SHOULD) stop ringing and the user should answer.

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

public class DEBUGCoroutines : MonoBehaviour
{
    //DIFFERENT STATES
    public enum DebugState { Ringing, Pickup, Stop }
    public DebugState state = DebugState.Ringing;

    Coroutine stateRoutine = null;

    void Start()
    {
        //START EXAMPLE BY RINGING
        UpdateState(state, true);
    }

    //STATE UPDATER, ON CALL CHANGES STATE
    void UpdateState(DebugState s, bool force = false)
    {
        if (s != state || force)
        {
            state = s;

            CustomStopAllCoroutines();
            switch (state)
            {
                case DebugState.Ringing:
                    CustomStartCoroutine(stateRoutine, Ring());
                    break;

                case DebugState.Pickup:
                    CustomStartCoroutine(stateRoutine, Pickup());

                    break;
                case DebugState.Stop:
                    CustomStartCoroutine(stateRoutine, Stop());
                    break;

                default:
                    break;
            }
        }

    }


    //STATE EVENTS
    //
    IEnumerator Pickup()
    {
        Debug.Log("HELLO!?");
        yield return new WaitForSeconds(5f);
        Debug.Log("Ok, Good bye");
        UpdateState(DebugState.Stop);
    }

    IEnumerator Ring()
    {
        while (state == DebugState.Ringing)
        {
            Debug.Log("ring");
            yield return new WaitForSeconds(2f);
        }
        Debug.Log("ERROR"); //THIS SHOULDN'T PLAY!
    }

    IEnumerator Stop()
    {
        yield return new WaitForSeconds(10);
        UpdateState(DebugState.Ringing);
    }

    
    //METHOD CALLED BY THE EVENT
    public void GetCall()
    {
        UpdateState(DebugState.Pickup);
    }


    //COROUTINE MANAGERS
    //
    void CustomStopAllCoroutines()
    {
        QuitRoutine(stateRoutine);
    }
    void CustomStartCoroutine(Coroutine cr, IEnumerator routine)
    {
        if (cr == null) cr = StartCoroutine(routine);
        else
        {
            StopCoroutine(cr);
            cr = null;
            cr = StartCoroutine(routine);
        }
    }

    void QuitRoutine(Coroutine cr)
    {
        if (cr != null) StopCoroutine(cr);
        cr = null;
    }
}

And here the simple event caller:

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

public class DEBUGCaller : MonoBehaviour
{
    public UnityEvent debugEvent;

    void Update()
    {
        if(Input.GetKeyDown(KeyCode.D))  debugEvent.Invoke();
    }
}

Found the solution and it was my mistake.


I was not really changing my Coroutine or IEnumerator variable (had to change to IEnumerator cause I could not define the Coroutine member from a Method Variable - I bet the ref keyword would do)


All I had to do was using the out keyword to save my IEnumerator outside the method, now everything is working smooth


The changed parts of the code:

//stateRoutine is now an IEnumerator
IEnumerator stateRoutine = null;

//In the switch now I had to use out
CustomStartCoroutine(out stateRoutine, Ring());

//This is now the fixed method to start coroutines
void CustomStartCoroutine(out IEnumerator cr, IEnumerator routine)
    {
        cr = routine;
        StartCoroutine(cr);
    }