KY Lu
KY Lu

Reputation: 439

Unity StopCoroutine does not stop coroutines when there are multi-layer (or nested) "yield return"

If I use one-layer yield return, StopCoroutine() can successfully stop my coroutine. See the code example below...

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

public class TestStopcoroutine : MonoBehaviour {
  IEnumerator co = null;

  // Use this for initialization
  void Start () {
    co = FunA();
    StartCoroutine(co);
  }

  private IEnumerator FunA() {
    Debug.Log("Enter FunA...");
    yield return RepeatPrint();
    Debug.Log("FunA end...");
  }

  private IEnumerator RepeatPrint() {
    for (int i = 0; i < 5; i++) {
      Debug.Log(i);
      yield return new WaitForSeconds(1);
    }
  }

  /// <summary>
  /// Set this function to a button on UI Canvas 
  /// </summary>
  public void OnCancelButtonClick() {
    if (co != null) {
      StopCoroutine(co);
      Debug.Log("Stop Coroutine...");
      co = null;
    }
  }
}

this output is...

// Enter FunA...
// 0
// 1
// 2
// 3
// Stop Coroutine...

However, if I add one layer (i.e.FunB()), FunA() will be stopped but the inside coroutine(FunB()) will not be stopped. See the example code below:

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

public class TestStopcoroutine : MonoBehaviour {
  IEnumerator co = null;

  void Start () {
    co = FunA();
    StartCoroutine(co);
  }

  private IEnumerator FunA() {
    Debug.Log("Enter FunA...");
    yield return FunB();
    Debug.Log("FunA end...");
  }

  private IEnumerator FunB () {
    Debug.Log("Enter FunB...");
    yield return RepeatPrint();
    Debug.Log("FunB end...");
  }

  private IEnumerator RepeatPrint() {
    for (int i = 0; i < 5; i++) {
      Debug.Log(i);
      yield return new WaitForSeconds(1);
    }
  }

  /// <summary>
  /// Set this function to a button on UI Canvas 
  /// </summary>
  public void OnCancelButtonClick() {
    if (co != null) {
      StopCoroutine(co);
      Debug.Log("Stop Coroutine...");
      co = null;
    }
  }
}

this output is...

// Enter FunA...
// Enter FunB...
// 0
// 1
// 2
// Stop Coroutine...
// 3
// 4
// FunB end...

Therefore, I am wondering why StopCoroutine() cannot successfully stop multi-layer yield return coroutine??

Upvotes: 5

Views: 9013

Answers (2)

Programmer
Programmer

Reputation: 125305

For your second code, the log should indeed end at Stop Coroutine.....

There are three possibilities to why it is showing the current output: (Very likely in order why this is happening)

1. You are setting Time.timeScale to 0. Search in your whole project and make sure that you're not doing this: Time.timeScale = 0;. This can pause or upause a coroutine function that's waiting with WaitForSeconds. If you did, temporary remove or comment it out and see if it's the issue.

2. Your project is corrupted and there is now a bug in it. Sometimes, a bug can randomly happen in a Unity project and the only way to fix that is to create a new project and manually move the resources from the old project to the new one.

Create a new project and test your modified code below.

3. A bug with Unity itself. Since you're using Unity 5.6.4p3, there is chance this is bug with Unity. If doing what's in #1 and #2 did not solve your issue then simply update Unity to the latest version (Unity 2018.xxx). This is more likely to fix your issue and don't forget to test with a new project instead of importing the old one.

Use the modified code from your question below to test #2 and #3. It uses the Invoke function to stop the coroutine after one second.

IEnumerator co = null;

void Start()
{
    co = FunA();
    Invoke("OnCancelButtonClick", 1f);
    StartCoroutine(co);
}

private IEnumerator FunA()
{
    Debug.Log("Enter FunA...");
    yield return FunB();
    Debug.Log("FunA end...");
}

private IEnumerator FunB()
{
    Debug.Log("Enter FunB...");
    yield return RepeatPrint();
    Debug.Log("FunB end...");
}

private IEnumerator RepeatPrint()
{
    for (int i = 0; i < 5; i++)
    {
        Debug.Log(i);
        yield return new WaitForSeconds(1);
    }
}

/// <summary>
/// Set this function to a button on UI Canvas 
/// </summary>
public void OnCancelButtonClick()
{
    if (co != null)
    {
        StopCoroutine(co);
        Debug.Log("Stop Coroutine...");
        co = null;
    }
}

The expected output:

Enter FunA...

Enter FunB...

0

Stop Coroutine...

To address KYL3R's statement in his answer that stopping the main Coroutine won't affect any other coroutine, that has already been started. This is partially true but totally depends on how and where the coroutine was started.

There are two ways to start a coroutine function:

1. Start a coroutine with the StartCoroutine function from any function like a function with a void return type.

void Start()
{
    StartCoroutine(RepeatPrint());
}

or

IEnumerator Start()
{
    yield return StartCoroutine(RepeatPrint());
}

2. Start a coroutine function without the StartCoroutine function by yielding the coroutine function you want to start. This must be done inside a coroutine function or a function with IEnumerator return type. It can't be done in a normal function like a void function.

IEnumerator Start()
{
    yield return RepeatPrint();
}

When you start a coroutine with StartCoroutine then start children coroutines after with StartCoroutine but then killed the parent coroutine, the children coroutine functions will and should continue to run untouched.

Now, when you start a coroutine with StartCoroutine then start children coroutines after with yield return YourCoroutine() without using the StartCoroutine function but then killed the parent coroutine, the children coroutine functions will and should terminate or stop immediately so as the parent one.

It's not surprising that most Unity users don't know because it is not documented but it's something that's very important to know when using coroutine in Unity.

Upvotes: 3

KYL3R
KYL3R

Reputation: 4073

Coroutines run independent from each other. So stopping the main Coroutine won't affect any other, that has already been started.

It seems you have 2 options:

A: Stop the nested Coroutine from inside the first level

  • Store a Reference to RepeatPrint and use that to stop it using StopCoroutine.

edit: actually "from inside the first level" is not necessary, just use the correct Reference.

B: Stop ALL Coroutines in your MonoBehaviour

  • May work if you have no other Coroutines running

Upvotes: -1

Related Questions