A recreated coroutine timeline. Disabling the owner GameObject stops the routine.
A recreated coroutine timeline. Disabling the owner GameObject stops the routine.

SetActive(false) stops the routine

A coroutine is not a separate thread. It stops when the owner GameObject's activeSelf becomes false or when the MonoBehaviour is destroyed. Disabling the component with enabled = false does not stop its coroutines by itself. That difference explains many silent fades, timers, and wait operations.

using System.Collections;
using UnityEngine;

public class FadeController : MonoBehaviour
{
    [SerializeField] private CanvasGroup target;

    public void Begin()
    {
        StartCoroutine(FadeOut());
    }

    private IEnumerator FadeOut()
    {
        while (target.alpha > 0f)
        {
            target.alpha = Mathf.MoveTowards(target.alpha, 0f, Time.deltaTime);
            yield return null;
        }
    }
}

Separate work from visuals

If the whole controller is disabled just to hide a panel, keep the controller alive and hide the CanvasGroup or a child object instead. When you do want to stop the work, keep the Coroutine handle and call StopCoroutine explicitly.

private Coroutine running;

public void Begin()
{
    if (running != null)
        StopCoroutine(running);

    running = StartCoroutine(FadeOut());
}

private void OnDisable()
{
    if (running != null)
        StopCoroutine(running);
}

A coroutine is still on the main thread

File I/O or heavy computation inside a coroutine still runs on the main thread. Coroutines spread work across frames, but they do not move CPU work to another core. Profile the cost first, then decide whether a Job System or async/await is appropriate.

Official documentation checkedUnity coroutines manual ↗