Reputation: 439
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
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
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
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
Upvotes: -1