J'essaie de mettre à jour une liste à l'intérieur de l'adaptateur en utilisant async, je peux voir qu'il y a trop de passe-partout.
Est-ce la bonne façon d'utiliser Kotlin Coroutines?
cela peut-il être optimisé davantage?
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()}
}
}
Après avoir lutté pendant plusieurs jours avec cette question, je pense que le modèle le plus simple et le plus clair pour l'attente asynchrone des activités Android utilisant Kotlin est:
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.
}
}
Les seules dépendances de Gradle pour les coroutines sont: kotlin-stdlib-jre7
, kotlinx-coroutines-Android
.
Remarque: Utilisez job.await()
à la place de job.join()
car await()
renvoie les exceptions, mais pas join()
. Si vous utilisez join()
, vous devrez vérifier job.isCompletedExceptionally
À la fin du travail.
Pour lancer des appels simultanés de conversion, vous pouvez procéder comme suit:
val jobA = async(CommonPool) { /* Blocking call A */ };
val jobB = async(CommonPool) { /* Blocking call B */ };
jobA.await();
jobB.await();
Ou:
val jobs = arrayListOf<Deferred<Unit>>();
jobs += async(CommonPool) { /* Blocking call A */ };
jobs += async(CommonPool) { /* Blocking call B */ };
jobs.forEach { it.await(); };
Comment lancer une coroutine
Dans la bibliothèque kotlinx.coroutines
, Vous pouvez créer une nouvelle coroutine en utilisant la fonction launch
ou async
.
Conceptuellement, async
est semblable à launch
. Il commence une coroutine séparée qui est un fil léger qui fonctionne en même temps que toutes les autres coroutines.
La différence est que le lancement retourne un Job
et ne porte aucune valeur résultante, alors que async
renvoie un Deferred
- un avenir léger non bloquant qui représente une promesse de fournir un résultat plus tard. Vous pouvez utiliser .await()
sur une valeur différée pour obtenir son résultat final, mais Deferred
est également un Job
, vous pouvez donc l'annuler si nécessaire.
contexte Coroutine
Dans Android, nous utilisons généralement deux contextes:
uiContext
pour répartir l'exécution sur le thread Android principal UI
= [(pour la coroutine parent)).bgContext
pour répartir l'exécution dans le thread d'arrière-plan (pour les coroutines enfants).Exemple
//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
Dans l'exemple suivant, nous allons utiliser CommonPool
pour bgContext
, ce qui limite le nombre de threads exécutés en parallèle à la valeur de Runtime.getRuntime.availableProcessors()-1
. Donc, si la tâche de coroutine est planifiée, mais que tous les cœurs sont occupés, elle sera mise en file d'attente.
Vous pouvez envisager d'utiliser newFixedThreadPoolContext
ou votre propre implémentation de pool de threads mis en cache.
lance + async (exécute la tâche)
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
}
lance + async + async (exécute deux tâches de manière séquentielle)
Remarque: task1 et task2 sont exécutés de manière séquentielle.
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
}
lance + async + async (exécute deux tâches en parallèle)
Remarque: task1 et task2 sont exécutés en parallèle.
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
}
Comment annuler une coroutine
La fonction loadData
renvoie un objet Job
qui peut être annulé. Lorsque la coroutine parent est annulée, tous ses enfants sont annulés de manière récursive.
Si la fonction stopPresenting
a été appelée alors que dataProvider.loadData
Était encore en cours, la fonction view.showData
Ne sera jamais appelée.
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 réponse complète est disponible dans mon article Android Coroutine Recipes
Je pense que vous pouvez vous débarrasser de runOnUiThread { ... }
en utilisant UI
contexte pour Android au lieu de CommonPool
.
Le contexte UI
est fourni par le module kotlinx-coroutines-Android .
Nous avons aussi une autre option. si nous utilisons la bibliothèque Anko , alors cela ressemble à ceci
doAsync {
// Call all operation related to network or other ui blocking operations here.
uiThread {
// perform all ui related operation here
}
}
Ajouter une dépendance pour Anko dans votre application gradé comme ceci.
compile "org.jetbrains.anko:anko:0.10.3"
Comme dit sdeff, si vous utilisez le contexte d'interface utilisateur, le code à l'intérieur de cette coroutine sera exécuté par défaut sur le thread d'interface utilisateur. Et, si vous avez besoin d'exécuter une instruction sur un autre thread, vous pouvez utiliser run(CommonPool) {}
De plus, si vous n'avez rien à renvoyer de la méthode, vous pouvez utiliser la fonction launch(UI)
au lieu de async(UI)
(la première retournera un Job
et le ce dernier a Deferred<Unit>
).
Un exemple pourrait être:
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 vous avez besoin d’aide supplémentaire, je vous recommande de lire le guide principal de kotlinx.coroutines et, en outre, le guide de coroutines + UI
Toutes les réponses ci-dessus sont exactes, mais j'avais du mal à trouver la bonne importation pour le UI
de kotlinx.coroutines
, il était en conflit avec UI
de Anko
. Ses
import kotlinx.coroutines.experimental.Android.UI
Voici la bonne façon d'utiliser Kotlin Coroutines. Coroutine scope suspend simplement la coroutine actuelle jusqu'à ce que toutes les coroutines enfants aient terminé leur exécution. Cet exemple nous montre explicitement comment child coroutine
travaille dans parent coroutine
.
Un exemple avec des explications:
fun main() = blockingMethod { // coroutine scope
launch {
delay(2000L) // suspends the current coroutine for 2 seconds
println("Tasks from some blockingMethod")
}
coroutineScope { // creates a new coroutine scope
launch {
delay(3000L) // suspends this coroutine for 3 seconds
println("Task from nested launch")
}
delay(1000L)
println("Task from coroutine scope") // this line will be printed before nested launch
}
println("Coroutine scope is over") // but this line isn't printed until nested launch completes
}
J'espère que cela t'aides.
Si vous voulez renvoyer quelque chose d'un fil de fond, utilisez async
launch(UI) {
val result = async(CommonPool) {
//do long running operation
}.await()
//do stuff on UI thread
view.setText(result)
}
Si le fil de fond ne renvoie rien
launch(UI) {
launch(CommonPool) {
//do long running operation
}.await()
//do stuff on UI thread
}