Quick answer: Store the Coroutine handle returned by StartCoroutine(Foo()); pass that handle to StopCoroutine. The string-based StopCoroutine only matches StartCoroutine calls that were also started by string.
You call StartCoroutine(Spawn()) then later StopCoroutine("Spawn") to halt it. The coroutine keeps running. The string and IEnumerator overloads of these methods don’t cross-reference each other.
The Symptom
StopCoroutine returns silently and the coroutine continues to fire. Or you call StopCoroutine in OnDisable but the coroutine still runs in the next OnEnable. No error.
The Fix
public class Spawner : MonoBehaviour
{
private Coroutine _spawnCo;
void OnEnable()
{
_spawnCo = StartCoroutine(SpawnLoop());
}
void OnDisable()
{
if (_spawnCo != null)
{
StopCoroutine(_spawnCo);
_spawnCo = null;
}
}
private IEnumerator SpawnLoop()
{
while (true)
{
SpawnOne();
yield return new WaitForSeconds(2f);
}
}
}
The handle pattern is unambiguous and works with any way of starting the coroutine.
Why String-Based Exists
StartCoroutine with a string allows starting by name when you don’t have an IEnumerator handle (rare). StopCoroutine with a string then matches by name. They’re a matched pair — using one form to start and the other to stop fails. Use IEnumerator + Coroutine handle everywhere; ignore the string overloads.
Disable / Destroy Lifecycle
Coroutines on a MonoBehaviour stop automatically when the MonoBehaviour is disabled or destroyed. So OnDisable cleanup is technically redundant for the auto-stop case. But explicit StopCoroutine + null lets you re-Start cleanly without checking if a previous one is still alive, and it’s necessary if the GameObject stays active but you want to halt the loop.
Pattern: Restart Cleanly
public void RestartSpawn()
{
if (_spawnCo != null)
StopCoroutine(_spawnCo);
_spawnCo = StartCoroutine(SpawnLoop());
}
Verifying
Add a print at the top of the coroutine loop body. Disable the GameObject. Print should stop. With the broken string-based stop pattern, the print continues until the GameObject is destroyed.
“Handle in, handle out. Match the start and stop forms. Coroutines stop when you ask.”
Related Issues
For coroutine not starting, see coroutine not starting. For async vs coroutine, see async exceptions.
Store the handle. Stop by handle. Coroutines obey.