Coroutine does not stop when having a coroutine inside.

Inside of my combat logic for my AI I use coroutines to time the behaviour. Basically I start a random coroutine containing one behaviour, wait, then stop the coroutine and repeat. However the stop coroutine part does not work for one behaviour which has another coroutine inside. This results in stacking those coroutines over time. Thus after a while I get an almost constant stream of bullets.

While i was able to work arround this issue by merging both coroutines I am still curious to find out why my initial approach did not work. I am still learning so every help at improving my comprehension is highly appreciated. Thanks in advance.

The essential parts of my code are the following:

function Start(){
	Combatlogic();
}
function CombatLogic() : IEnumerator{
	while(true){
		if(!sleeping){
			yield ChooseBehaviour(healthRatio);
		}
	yield;
	}
}
function ChooseBehaviour(modi : float) : IEnumerator{
	//some code
	}else if (** randomized condition **){
		//Debug.Log("Used Fire");
		StartCoroutine( "executeShootNeedle");
		yield WaitForSeconds(needleFireTime);
		StopCoroutine( "executeShootNeedle");
	} 
//some more code
}
function ShootNeedle() : IEnumerator{
	yield WaitForSeconds(needleAim);
	//attack code
	yield WaitForSeconds( needleDelay);
}
function executeShootNeedle() : IEnumerator{
	while(true){
		fireTransform.LookAt(target.position);
		yield ShootNeedle();
	}
}

Actually, I don’t think Bunny’s answer is quite correct. I’ve turned your example into the equivalent c#, and have it posted below (hence the need for an answer, with formatting, rather than a comment). You can create a new project and attach this script (as “test.cs”) to the main camera, and see the original behavior.

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

public class test : MonoBehaviour 
{ 
	void Start()
	{
		StartCoroutine(Example());
	}
	
    IEnumerator Example() {
        StartCoroutine("executeShootNeedle");
		yield return new WaitForSeconds(10);
		print("Attempting to stop the coroutine...");
        StopCoroutine("executeShootNeedle");
    }	
	
	IEnumerator ShootNeedle() 
	{
		print ("Aim...");
	    yield return new WaitForSeconds(3);
	    
		print ("Fire!");
	    yield return new WaitForSeconds(1);
	}
	IEnumerator executeShootNeedle()
	{
	    while(true){
			print ("Starting firing sequence.");
			yield return StartCoroutine(ShootNeedle());
	    }
	}
}

What’s very interesting to note is this: StopCoroutine() is clearly getting called; however, executeShootNeedle() (and therefore ShootNeedle()) continues operating after this. If what Bunny said were true, ShootNeedle() should continue running, but executeShootNeedle() should stop running. As we can see from the logs, this is not the case.

The conclusion is that StopCoroutine() is not functioning at all. According to how I interpret the docs, this is a bug. I wonder what’s going on under the covers?

Edit:

In addition, I should also point out that if you simply remove the “yield return StartCoroutine…” call from executeShootNeedle(), the stop works correctly. For example:

	IEnumerator executeShootNeedle()
	{
	    while(true){
			print ("Starting firing sequence.");
			yield return new WaitForSeconds(2);
	    }
	}

Another Edit: another thing I tried was to add a “yield return 0;” after the call to ShootNeedle(), like so:

	IEnumerator executeShootNeedle()
	{
	    while(true){
			print ("Starting firing sequence.");
			yield return StartCoroutine(ShootNeedle());
			yield return 0;
	    }
	}

My thinking was that perhaps “yield return StartCoroutine()” didn’t give StopCoroutine() a chance to actually stop the coroutine. But even with this addition, StopCoroutine() has no effect on anything.

Sure, each coroutine runs on it’s own. When you start a coroutine and yield on this coroutine the calling coroutine just waits for the “nested” coroutine to finish. When you stop the calling coroutine it won’t stop the coroutine you started in this coroutine.

The best fix would be to not use a coroutine for ShootNeedle and place the yields in ExecuteShootNeedle:

function ShootNeedle(){ // Not a coroutine anymore
    //attack code
}
function executeShootNeedle() : IEnumerator{
    while(true){
        fireTransform.LookAt(target.position);
        yield WaitForSeconds(needleAim);
        ShootNeedle(); // just call it as normal function
        yield WaitForSeconds( needleDelay);
    }
}

Stopcoroutine can only halt a coroutine while it’s yielding. Keep in mind Unity don’t uses threads for the scripting. All your scripts and coroutines are running sequential in the main-thread. Whenever a coroutine yields another coroutine or just the gameloop can continue.