CoroutineScope — qué es, ciclo de vida y funcionamiento en corrutinas

Autor: IT Sectr Publicado: 2026-06-22 Tiempo de lectura: 10 min

CoroutineScope es una interfaz de Kotlin que define el ciclo de vida de una corrutina y proporciona un contexto para lanzar nuevas corrutinas. Según la documentación de Kotlin, 2025, cada instancia de CoroutineScope contiene un CoroutineContext y gestiona todas las corrutinas lanzadas dentro de él. Cuando el scope se cancela, todas las corrutinas hijas se cancelan automáticamente, evitando fugas de memoria.

Puntos clave

  • CoroutineScope — una interfaz con un único campo CoroutineContext, que define el ciclo de vida de las corrutinas
  • Job — un elemento de contexto responsable de la cancelación: cancelar el scope cancela todas las corrutinas hijas
  • Concurrencia estructurada — el principio por el cual las corrutinas hijas están vinculadas al scope padre
  • GlobalScope — un scope para toda la aplicación que no se recomienda debido al riesgo de fugas de memoria
  • supervisorScope — un scope especial donde cancelar una corrutina hija no cancela las demás

¿Qué es CoroutineScope en Kotlin?

CoroutineScope es una interfaz fundamental de la librería kotlinx.coroutines que sirve como contenedor para corrutinas. Define los límites del ciclo de vida de las corrutinas: cuando el scope se completa, todas las corrutinas dentro de él se cancelan automáticamente.

kotlin
public interface CoroutineScope {
    public val coroutineContext: CoroutineContext
}

La interfaz contiene solo un campo — coroutineContext. A través de él, el scope proporciona un despachador (Dispatcher), un trabajo (Job), un manejador de excepciones y otros elementos de contexto para todas las corrutinas lanzadas dentro de él.

Rol en la librería kotlinx.coroutines

Todas las funciones de lanzamiento de corrutinas — launch, async, runBlocking — son funciones de extensión en CoroutineScope. Esto significa que solo se pueden llamar cuando hay un objeto scope disponible. Este diseño garantiza que cada corrutina tenga un padre y un ciclo de vida claramente definidos.

¿Dónde se usa CoroutineScope?

En Android, cada componente arquitectónico tiene su propio scope: viewModelScope para ViewModel, lifecycleScope para Activity/Fragment. En aplicaciones de servidor, un scope puede estar vinculado a una solicitud HTTP o a un grupo de conexiones de base de datos.

Cómo funciona CoroutineScope: Job y concurrencia estructurada

Entender el funcionamiento interno de CoroutineScope requiere familiaridad con el concepto de Job y el principio de concurrencia estructurada.

Job — la tarea de la corrutina

Cada corrutina al lanzarse devuelve un objeto Job (o Deferred para async). Job representa una tarea con un ciclo de vida finito: New, Active, Completing, Completed, Cancelling, Cancelled. Los objetos Job forman una estructura de árbol:

  • Job padre — el scope en el que se lanzó la corrutina
  • Job hijo — cada corrutina lanzada mediante launch/async
  • Cancelación del padre → cancelación de todos los hijos
  • Excepción en un hijo → cancelación del padre (excepto en supervisorScope)

El principio de concurrencia estructurada

Concurrencia estructurada es un principio arquitectónico clave de Kotlin Coroutines, donde el ciclo de vida de la corrutina está vinculado al ciclo de vida de su scope. Esto contrasta con el modelo “dispara y olvida”, donde una corrutina continúa viviendo después de que el scope se completa. Ventajas de la concurrencia estructurada:

  • Ciclo de vida predecible — cuando el scope se completa, todas las corrutinas están garantizadas de detenerse
  • Manejo automático de errores — una excepción en cualquier corrutina hija se propaga al scope
  • Sin fugas de memoria — ninguna corrutina permanece ejecutándose después de que el scope se completa
  • Jerarquía clara — el código refleja la estructura lógica de las operaciones paralelas

Ciclo de vida de CoroutineScope

Cuando se llama a scope.cancel(), el Job del scope transiciona al estado Cancelled, que cancela recursivamente todos los Jobs hijos. Después de la cancelación, el scope solo se puede reutilizar si se crea una nueva instancia de CoroutineScope.

Creación y configuración de CoroutineScope

Puedes crear un CoroutineScope mediante una función de fábrica o implementando la interfaz en tu clase. Veamos ambos enfoques.

Función de fábrica CoroutineScope()

kotlin
val scope = CoroutineScope(Dispatchers.Default + SupervisorJob())

scope.launch {
    println("Running on ${Thread.currentThread().name}")
}

La función de fábrica toma un CoroutineContext y crea un scope con el contexto especificado. El ejemplo usa Dispatchers.Default para tareas intensivas de CPU y SupervisorJob, que aísla las excepciones entre corrutinas hijas.

Implementación de la interfaz mediante composición

kotlin
class MyRepository {
    private val scope = CoroutineScope(Dispatchers.IO + Job())

    suspend fun fetchData(): Data = scope.async {
        api.getData()
    }.await()

    fun cleanup() {
        scope.cancel()
    }
}

Almacenamos el scope como un campo de clase y llamamos manualmente a cleanup para cancelarlo. Este enfoque es adecuado para componentes con un ciclo de vida gestionado, por ejemplo, repositorios o gestores.

Implementación mediante delegación

Kotlin permite delegar la implementación de CoroutineScope mediante la palabra clave by:

kotlin
class DataLoader : CoroutineScope by CoroutineScope(Dispatchers.IO) {
    fun load() {
        launch {
            // coroutine runs in DataLoader scope
        }
    }
}

Este enfoque es conveniente cuando la clase misma es un scope y quiere proporcionar métodos de lanzamiento de corrutinas. Sin embargo, ten cuidado: la clase hereda todos los métodos de CoroutineScope, incluido cancel, lo que puede romper la encapsulación.

GlobalScope vs CoroutineScope personalizado

GlobalScope es un singleton de CoroutineScope para toda la aplicación. Su uso en código de producción no se recomienda oficialmente.

Problemas con GlobalScope

  • Falta de concurrencia estructurada — las corrutinas en GlobalScope no están vinculadas al ciclo de vida del componente
  • Fugas de memoria — una corrutina puede continuar ejecutándose después de cerrar la Activity/Fragment
  • Pruebas difíciles — GlobalScope no se puede reemplazar en pruebas
  • Consumo no controlado de recursos — muchas corrutinas pueden ejecutarse más tiempo del esperado

Cuándo está justificado GlobalScope

JetBrains permite GlobalScope solo en escenarios raros: procesos en segundo plano a nivel de aplicación que deben vivir incluso después de cerrar todas las Activities (por ejemplo, sincronización de datos, analítica). Pero incluso en estos casos, es preferible crear tu propio scope con CoroutineScope(SupervisorJob()).

Recomendación

Usa siempre un CoroutineScope personalizado con gestión explícita del ciclo de vida. En Android, estos son viewModelScope y lifecycleScope. En aplicaciones de servidor, crea un scope para cada solicitud o grupo de conexiones.

coroutineScope vs supervisorScope: cuál es la diferencia

Ambas funciones son funciones suspend que crean un scope temporal para tareas paralelas, pero su comportamiento con excepciones difiere fundamentalmente.

CaracterísticacoroutineScopesupervisorScope
Comportamiento ante erroresUna excepción en una corrutina hija cancela todas las demásUna excepción en una corrutina hija NO cancela las demás
Propagación de erroresSí, la primera excepción se propaga hacia afueraSí, la primera excepción se propaga hacia afuera
Job por defectoJob() — los hijos están vinculados al padreSupervisorJob() — los hijos no dependen entre sí
Caso de uso típicoOperación atómica de varios pasosTareas paralelas independientes (cargas de UI)

Cuándo elegir coroutineScope

Usa coroutineScope cuando múltiples operaciones paralelas forman una única operación atómica. Por ejemplo, cargar datos de tres servidores: si una solicitud falla, las demás no tienen sentido.

kotlin
suspend fun loadProductPage(): ProductPage = coroutineScope {
    val product = async { api.getProduct() }
    val reviews = async { api.getReviews() }
    ProductPage(product.await(), reviews.await())
}

Si getProduct o getReviews lanzan una excepción — ambas corrutinas se cancelan y la excepción se propaga al código llamante.

Cuándo elegir supervisorScope

Usa supervisorScope cuando las operaciones paralelas no dependen entre sí. Por ejemplo, cargar datos de perfil en varias secciones independientes: si la sección de recomendaciones falla, el encabezado del perfil y la lista de amigos deben mostrarse igualmente.

Errores comunes al trabajar con CoroutineScope

Veamos los errores más comunes de los desarrolladores al usar CoroutineScope en Kotlin.

Error 1: Olvidar cancelar el scope

El escenario más común de fuga de corrutinas es crear un scope sin llamar a cancel cuando el componente finaliza. Si el scope no se cancela, las corrutinas continúan ejecutándose, manteniendo referencias a objetos. En Android, usa viewModelScope o lifecycleScope, que se cancelan automáticamente.

Error 2: Usar GlobalScope en Activity o Fragment

GlobalScope ignora el ciclo de vida de los componentes de Android. Una corrutina lanzada en GlobalScope después de cerrar una Activity continuará ejecutándose e intentará actualizar la UI — lo que provoca un fallo. Usa siempre lifecycleScope para componentes de UI.

Error 3: Reutilizar un scope cancelado

Después de llamar a cancel(), el scope no se puede reutilizar — todas las corrutinas dentro de él ya están completadas. Crea una nueva instancia de CoroutineScope mediante la función de fábrica. Job() no soporta reactivación.

Error 4: Delegación incorrecta de la interfaz CoroutineScope

Al delegar con by, la clase obtiene un método cancel() público que puede ser llamado desde cualquier lugar, rompiendo la encapsulación. Almacena el scope como un campo privado en lugar de delegar la interfaz.

Preguntas frecuentes

¿En qué se diferencia CoroutineScope de CoroutineContext?

CoroutineScope es una interfaz que posee un CoroutineContext y es responsable del ciclo de vida de las corrutinas. CoroutineContext es un conjunto de elementos (despachador, job, manejador de errores) que define “cómo” se ejecuta una corrutina. Una diferencia: el scope crea corrutinas, mientras que el contexto controla su comportamiento.

¿Se puede crear un CoroutineScope con SupervisorJob?

Sí, es un patrón estándar: CoroutineScope(Dispatchers.IO + SupervisorJob()). SupervisorJob previene la cancelación en cascada de las corrutinas hijas cuando una de ellas lanza una excepción. Esto es útil para tareas paralelas independientes donde un error en una no debe detener a las demás.

¿Cuántas corrutinas puede contener un CoroutineScope?

No hay límite en la cantidad de corrutinas en un scope — solo están limitadas por la memoria disponible y la configuración del despachador. El límite práctico es típicamente de miles de corrutinas activas en un solo scope. Sin embargo, una gran cantidad de corrutinas puede indicar problemas arquitectónicos.

¿Cómo probar código con CoroutineScope?

La forma correcta es pasar el scope a la clase mediante el constructor o usar runBlockingTest / runTest de kotlinx-coroutines-test. En las pruebas, puedes reemplazar el scope con TestCoroutineDispatcher y controlar la ejecución de las corrutinas manualmente.

¿Puede una corrutina tener su propio scope?

No, un scope es un contenedor externo para una corrutina. La corrutina en sí misma no es un scope. Sin embargo, dentro de una corrutina puedes crear un nuevo scope mediante coroutineScope o supervisorScope para lanzar corrutinas hijas en paralelo.

Resumen

  • CoroutineScope — una interfaz con un campo coroutineContext que define el ciclo de vida de las corrutinas lanzadas dentro de él
  • Concurrencia estructurada — cancelar el scope cancela automáticamente todas las corrutinas hijas, evitando fugas de memoria
  • Job y SupervisorJob — dos modos de manejo de errores: cancelación en cascada (Job) y errores aislados (SupervisorJob)
  • GlobalScope — no recomendado para producción por falta de vinculación al ciclo de vida
  • coroutineScope vs supervisorScope — operaciones paralelas atómicas vs tareas paralelas independientes
  • viewModelScope y lifecycleScope — scopes listos para Android, cancelados automáticamente al finalizar el componente
  • Función de fábrica — la forma preferida de crear un scope mediante CoroutineContext + llamada explícita a cancel

Desarrollaremos una aplicación móvil llave en mano

IT Sectr crea aplicaciones para iOS y Android para startups y empresas desde 2017. Le asesoraremos y le propondremos la mejor solución.

Discutir el proyecto

Lea también