¿Por qué viewModelScope.launch se ejecuta en el subproceso principal de forma predeterminada?

5 minutos de lectura

Mientras estaba aprendiendo rutinas y cómo usarlas correctamente en una aplicación de Android, encontré algo que me sorprendió.

Al iniciar una rutina usando viewModelScope.launch { } y al establecer un punto de interrupción dentro de la lambda de lanzamiento, noté que mi aplicación ya no respondía porque todavía estaba en el hilo principal.

Esto me confunde porque los documentos de viewModelScope.launch { } decir claramente:

Lanza una nueva rutina sin bloquear el hilo actual

¿No es el hilo actual el hilo principal? ¿Cuál es el propósito del lanzamiento si no se ejecuta en un subproceso diferente de forma predeterminada?

Pude ejecutarlo en otro hilo usando viewModelScope.launch(Dispatchers.IO){ } que funciona como esperaba, es decir, en otro hilo.

Lo que estoy tratando de lograr desde el launch El método es llamar a un repositorio y hacer algún trabajo de IO, es decir, llamar a un servicio web y almacenar los datos en una base de datos de la habitación. Así que estaba pensando en llamar viewModelScope.launch(Dispatchers.IO){ } haga todo el trabajo en un hilo diferente y al final actualice el resultado de LiveData.

viewModelScope.launch(Dispatchers.IO){
liveData.postValue(someRepository.someWork())
}

Así que mi segunda pregunta es, ¿es este el camino a seguir?

  • Gracias a esta pregunta, y después de un año de asumir viewModelScope.launch sería suficiente para mover el trabajo fuera de Main, actualicé mi método para ejecutar el cuerpo de la función envuelto en withContext(Dispatchers.IO) ¡y de repente el rendimiento de mi aplicación se disparó!

    – rmirabelle

    19 mayo 2021 a las 20:47

ViewModelScope.launch { } se ejecuta en el subproceso principal, pero también le da la opción de ejecutar otros despachadores, por lo que puede tener operaciones de interfaz de usuario y de fondo ejecutándose sincrónicamente.

Para ti ejemplo:

fun thisWillRunOnMainThread() {

    viewModelScope.launch { 

        //below code will run on UI thread.
        showLoadingOnUI()

        //using withContext() you can run a block of code on different dispatcher
        val result = novel.id = withContext(Dispatchers.IO) {
            withsomeRepository.someWork()
        }

        //The below code waits until the above block is executed and the result is set.
        liveData.value = result
        finishLoadingOnUI()
    }
}

Para obtener más referencia, diría que hay algunos artículos interesantes que lo ayudarán a comprender este concepto.

Enlace medio que lo explica muy bien.

  • Así es como se escribió mi código, pero después de guardar el valor necesario, no puedo mostrar los datos porque el control no va a la actividad desde ViewModel. Atrapado con esto durante semanas

    – Meenohara

    30 de julio de 2021 a las 13:27

avatar de usuario
EpicPandaFuerza

Así que mi segunda pregunta es, ¿es este el camino a seguir?

Esperaría que dos cosas fueran diferentes en su enfoque actual.

1.) El primer paso sería definir el programador de la operación en segundo plano a través de withContext.

class SomeRepository {
    suspend fun doWork(): SomeResult = withContext(Dispatchers.IO) {
        ...
    }
}

De esta manera, la operación en sí se ejecuta en un subproceso en segundo plano, pero no forzó que su alcance original fuera “fuera del subproceso”.

2.) Jetpack Lifecycle KTX proporciona la liveData { constructor de rutinas para que no tengas que hacerlo postValue a él manualmente.

val liveData: LiveData<SomeResult> = liveData {
    emit(someRepository.someWork())
}

Que en un ViewModel, usarías así:

val liveData: LiveData<SomeResult> = liveData(context = viewModelScope.coroutineContext) {
    withContext(Dispatchers.IO) {
        emit(someRepository.someWork())
    }
}

Y ahora puede activar automáticamente la carga de datos a través de la observación y no tener que invocar manualmente viewModelScope.launch {}.

  • viewModelScope.launch{ } solo se ejecuta en la primera llamada. segundo no llama

    – Nitish

    15 de febrero de 2021 a las 13:02


  • @Nitish no está seguro de a qué se refiere exactamente, ¿quiere decir cómo cuando navega a la pantalla, entonces? liveData { se activa y ejecuta el bloque?

    – EpicPandaForce

    15 de febrero de 2021 a las 13:04

  • Gracias por la respuesta rápida. en realidad, estoy llamando a una función en onResume of fragment. En esa función, obtengo algunos datos de Room DB. En esa función viewModelScope.launch { withContext(Dispachers.IO){ db.cbDao().getAll() } }. pero mi depurador no entra en el ámbito de lanzamiento.

    – Nitish

    15 de febrero de 2021 a las 13:16


  • sí, el depurador no es muy bueno cuando se trata de corrutinas o especialmente de flujos

    – EpicPandaForce

    15 de febrero de 2021 a las 13:50

  • @Amos tienes razón, viewModelScope.coroutineContext se debe pasar a liveData {. Actualizado.

    – EpicPandaForce

    13 de septiembre de 2021 a las 11:19

La idea detrás de que el subproceso principal sea predeterminado es que puede ejecutar operaciones de interfaz de usuario sin tener que cambiar el contexto. Es una convención que supongo que los escritores de la biblioteca coroutine de Kotlin han elegido

Supongamos que si, de manera predeterminada, si el lanzamiento se ejecuta en el subproceso IO, el código se vería así

viewmodelScope.launch {
  val response = networkRequest() 
  withContext(Dispatchers.Main) {
    renderUI(response)
  } 
}

Supongamos que, de forma predeterminada, si el lanzamiento se ejecuta en el subproceso predeterminado, el código se vería así

viewmodelScope.launch {
  val response: Response = null
  withContext(Dispatchers.IO) {
     response = networkRequest()
  }
  withContext(Dispatchers.Main) {
    renderUI(response)
  } 
}

Dado que el inicio predeterminado está en el hilo principal, ahora debe hacerlo a continuación

viewmodelScope.launch {
  val response: Response = null
  withContext(Dispatchers.IO) {
     response = networkRequest()
  }
  renderUI(response)
}

Para evitar que el código desordenado inicialice la respuesta con nulo, también podemos hacer que networkRequest se suspenda y envuelva la lógica empresarial de la función networkRequest() en withContext(Dispatchers.IO) y así es como mucha gente escribe su función networkRequest() también ! Espero que esto tenga sentido

Una de las principales razones por las que se ejecuta en el subproceso principal es porque es más práctico para el uso general en ViewModel, como escribió murali kurapati. Fue una elección de diseño.

También es importante tener en cuenta que todas las funciones de suspensión deben ser “principales seguras” de acuerdo con mejores practicas. Eso significa que su repositorio debería cambiar el contexto de la rutina en consecuencia, así:

class someRepository(private val ioDispatcher: CoroutineDispatcher) {
    suspend fun someWork() {
        withContext(ioDispatcher) {
            TODO("Heavy lifting")
        }
    }
}

¿Ha sido útil esta solución?