lib kotlinx coroutines android async-await kotlin coroutine

android - kotlinx - kotlin coroutines lib



Kotlin Coroutines de la manera correcta en Android (7)

Estoy tratando de actualizar una lista dentro del adaptador usando async, puedo ver que hay demasiada repetición.

¿Es la forma correcta de usar Kotlin Coroutines?

¿Se puede optimizar más esto?

fun loadListOfMediaInAsync() = async(CommonPool) { try { //Long running task adapter.listOfMediaItems.addAll(resources.getAllTracks()) runOnUiThread { adapter.notifyDataSetChanged() progress.dismiss() } } catch (e: Exception) { e.printStackTrace() runOnUiThread {progress.dismiss()} } catch (o: OutOfMemoryError) { o.printStackTrace() runOnUiThread {progress.dismiss()} } }


Como dice sdeff, si usa el contexto de la interfaz de usuario, el código dentro de esa rutina se ejecutará en el hilo de la interfaz de usuario de forma predeterminada. Y, si necesita ejecutar una instrucción en otro subproceso, puede usar run(CommonPool) {}

Además, si no necesita devolver nada del método, puede usar la función de launch(UI) lugar de async(UI) (la primera devolverá un Job y la última una Deferred<Unit> ).

Un ejemplo podría ser:

fun loadListOfMediaInAsync() = launch(UI) { try { withContext(CommonPool) { //The coroutine is suspended until run() ends adapter.listOfMediaItems.addAll(resources.getAllTracks()) } adapter.notifyDataSetChanged() } catch(e: Exception) { e.printStackTrace() } catch(o: OutOfMemoryError) { o.printStackTrace() } finally { progress.dismiss() } }

Si necesitas más ayuda te recomiendo que leas la guía principal de kotlinx.coroutines y, además, la guía de coroutines + UI.


Creo que puedes deshacerte de runOnUiThread { ... } usando el contexto de UI para aplicaciones de Android en lugar de CommonPool .

El contexto de UI lo proporciona el kotlinx-coroutines-android .


Después de luchar con esta pregunta durante días, creo que el patrón asíncrono-await más simple y claro para las actividades de Android que utilizan Kotlin es:

override fun onCreate(savedInstanceState: Bundle?) { //... loadDataAsync(); //"Fire-and-forget" } fun loadDataAsync() = async(UI) { try { //Turn on busy indicator. val job = async(CommonPool) { //We''re on a background thread here. //Execute blocking calls, such as retrofit call.execute().body() + caching. } job.await(); //We''re back on the main thread here. //Update UI controls such as RecyclerView adapter data. } catch (e: Exception) { } finally { //Turn off busy indicator. } }

Las únicas dependencias de Gradle para coroutines son: kotlin-stdlib-jre7 , kotlinx-coroutines-android .

Nota: use job.await() lugar de job.join() porque await await() vuelve a emitir las excepciones, pero join() no. Si utiliza join() , deberá comprobar job.isCompletedExceptionally vez que job.isCompletedExceptionally el trabajo.

Para iniciar llamadas de adaptación simultáneas , puede hacer esto:

val jobA = async(CommonPool) { /* Blocking call A */ }; val jobB = async(CommonPool) { /* Blocking call B */ }; jobA.await(); jobB.await();

O:

val jobs = arrayListOf<Deferred<Unit>>(); jobs += async(CommonPool) { /* Blocking call A */ }; jobs += async(CommonPool) { /* Blocking call B */ }; jobs.forEach { it.await(); };


Si quieres devolver algo del hilo de fondo usa async

launch(UI) { val result = async(CommonPool) { //do long running operation }.await() //do stuff on UI thread view.setText(result) }

Si el hilo de fondo no devuelve nada.

launch(UI) { launch(CommonPool) { //do long running operation }.await() //do stuff on UI thread }


También tenemos otra opción. Si usamos la biblioteca de Anko , entonces se ve así

doAsync { // Call all operation related to network or other ui blocking operations here. uiThread { // perform all ui related operation here } }

Agregue dependencia para Anko en su aplicación gradle como esta.

compile "org.jetbrains.anko:anko:0.10.3"


Todas las respuestas anteriores son correctas, pero me estaba costando mucho encontrar la importación correcta para la UI de UI de kotlinx.coroutines , estaba en conflicto con la UI de UI de Anko . Sus

import kotlinx.coroutines.experimental.android.UI


Como lanzar una coroutine.

En la biblioteca kotlinx.coroutines puede iniciar una nueva rutina utilizando la función de launch o async .

Conceptualmente, async es como el launch . Comienza una coroutine separada que es un hilo liviano que funciona simultáneamente con todas las otras coroutines.

La diferencia es que el lanzamiento devuelve un Job y no tiene ningún valor resultante, mientras que async devuelve un Deferred : un futuro ligero y sin bloqueo que representa una promesa de proporcionar un resultado más adelante. Puede usar .await() en un valor diferido para obtener su resultado final, pero Deferred también es un Job , por lo que puede cancelarlo si es necesario.

Contexto coroutine

En Android usualmente usamos dos contextos:

  • uiContext para enviar la ejecución en el subproceso principal de la UI Android (para la coroutine principal) .
  • bgContext para enviar la ejecución en el subproceso en segundo plano (para las secundarias secundarias) .

Ejemplo

//dispatches execution onto the Android main UI thread private val uiContext: CoroutineContext = UI //represents a common pool of shared threads as the coroutine dispatcher private val bgContext: CoroutineContext = CommonPool

En el siguiente ejemplo, usaremos CommonPool para bgContext que limita el número de subprocesos que se ejecutan en paralelo al valor de Runtime.getRuntime.availableProcessors()-1 . Entonces, si la tarea de rutina está programada, pero todos los núcleos están ocupados, se pondrá en cola.

Es posible que desee considerar el uso de newFixedThreadPoolContext o su propia implementación del grupo de subprocesos en caché.

launch + async (ejecutar tarea)

private fun loadData() = launch(uiContext) { view.showLoading() // ui thread val task = async(bgContext) { dataProvider.loadData("Task") } val result = task.await() // non ui thread, suspend until finished view.showData(result) // ui thread }

launch + async + async (ejecutar dos tareas secuencialmente)

Nota: task1 y task2 se ejecutan secuencialmente.

private fun loadData() = launch(uiContext) { view.showLoading() // ui thread // non ui thread, suspend until task is finished val result1 = async(bgContext) { dataProvider.loadData("Task 1") }.await() // non ui thread, suspend until task is finished val result2 = async(bgContext) { dataProvider.loadData("Task 2") }.await() val result = "$result1 $result2" // ui thread view.showData(result) // ui thread }

launch + async + async (ejecuta dos tareas en paralelo)

Nota: task1 y task2 se ejecutan en paralelo.

private fun loadData() = launch(uiContext) { view.showLoading() // ui thread val task1 = async(bgContext) { dataProvider.loadData("Task 1") } val task2 = async(bgContext) { dataProvider.loadData("Task 2") } val result = "${task1.await()} ${task2.await()}" // non ui thread, suspend until finished view.showData(result) // ui thread }

Como cancelar una coroutina.

La función loadData devuelve un objeto de Job que puede cancelarse. Cuando se cancela la matriz de padres, todos sus hijos también se cancelan de forma recursiva.

Si se stopPresenting función stopPresenting mientras dataProvider.loadData aún estaba en progreso, la función view.showData nunca será llamada.

var job: Job? = null fun startPresenting() { job = loadData() } fun stopPresenting() { job?.cancel() } private fun loadData() = launch(uiContext) { view.showLoading() // ui thread val task = async(bgContext) { dataProvider.loadData("Task") } val result = task.await() // non ui thread, suspend until finished view.showData(result) // ui thread }

La respuesta completa está disponible en mi artículo Recetas de Android Coroutine