Tengo el siguiente código (pseudocódigo)
fun onMapReady() { //do some stuff on current thread (main thread) //get data from server GlobalScope.launch(Dispatchers.IO){ getDataFromServer { result-> //update UI on main thread launch(Dispatchers.Main){ updateUI(result) //BREAKPOINT HERE NEVER CALLED } } } } Como se indica allí como comentario, el código nunca ingresa a la rutina y se envía a la cola principal. Sin embargo, lo siguiente funciona si uso explícitamente GlobalScope.launch(Dispatchers.Main) en lugar de simplemente launch(Dispatchers.Main)
fun onMapReady() { //do some stuff on current thread (main thread) //get data from server GlobalScope.launch(Dispatchers.IO){ getDataFromServer { result-> //update UI on main thread GlobalScope.launch(Dispatchers.Main){ updateUI(result) //BREAKPOINT HERE IS CALLED } } } }¿Por qué no funciona el primer enfoque?
Creo que el problema aquí es que getDataFromServer() es asíncrono, regresa inmediatamente y, por lo tanto, invoca launch(Dispatchers.Main) después de salir del GlobalScope.launch(Dispatchers.IO) { ... } . En otras palabras: intenta iniciar una corrutina utilizando un ámbito de corrutina que ya ha terminado.
Mi sugerencia es no mezclar API asincrónicas basadas en devolución de llamadas con rutinas como esta. Las rutinas funcionan mejor con funciones de suspensión que son síncronas. Además, si prefiere ejecutar todo de forma asíncrona e independiente de otras tareas (su onMapReady() inició 3 operaciones asíncronas separadas), entonces creo que las corrutinas no son una buena opción.
Hablando de su ejemplo: ¿está seguro de que no puede ejecutar getDataFromServer() directamente desde el hilo principal? No debería bloquear el hilo principal ya que es asíncrono. De manera similar, en algunas bibliotecas, las devoluciones de llamada se ejecutan automáticamente en el hilo principal y, en tal caso, su ejemplo podría reemplazarse simplemente con:
fun onMapReady() { getDataFromServer { result-> updateUI(result) } } Si el resultado se ejecuta en un subproceso en segundo plano, puede usar GlobalScope.launch(Dispatchers.Main) como lo hizo, pero esta no es realmente la forma habitual en que usamos las corrutinas. O puede usar utilidades como, por ejemplo , runOnUiThread() en Android, lo que probablemente tenga más sentido.
@broot ya explicó la esencia del problema. Está intentando launch una corrutina en el ámbito secundario del GlobalScope.launch externo, pero ese ámbito ya está listo cuando se llama a la devolución de llamada de getDataFromServer .
En resumen, no capture el alcance externo en una devolución de llamada que se llamará en un lugar/tiempo que no controla.
Una mejor manera de lidiar con su problema sería hacer que getDataFromServer suspenda en lugar de basarse en la devolución de llamada. Si es una API que no controla, puede crear un contenedor de suspensión de esta manera:
suspend fun getDataFromServerSuspend(): ResultType = suspendCoroutine { cont -> getDataFromServer { result -> cont.resume(result) } }A continuación, puede simplificar su código de llamada:
fun onMapReady() { // instead of GlobalScope, please use viewModelScope or lifecycleScope, // or something more relevant (see explanation below) GlobalScope.launch(Dispatchers.IO) { val result = getDataFromServer() // you don't need a separate coroutine, just a context switch withContext(Dispatchers.Main) { updateUI(result) } } } Como nota al margen, GlobalScope probablemente no sea lo que desea aquí. En su lugar, debe usar un alcance que se asigne al ciclo de vida de su vista o modelo de vista ( viewModelScope o lifecycleScope ) porque no está interesado en el resultado de esta rutina si la vista se destruye (por lo que debe cancelarse). Esto evitará fugas de corrutina si, por alguna razón, algo cuelga o gira dentro de la corrutina.