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 laUI
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