4
votes

Comment attendre la fin d'une coroutine

J'ai un code ci-dessous. Le délai (3000) est juste le remplacement d'une longue boucle (ou cycle). Je m'attends à ce qu'après la fin de la boucle, println(res) affiche "Some String" puis active le button . Mais dans la vraie vie println(res) imprime une chaîne vide et le button est devenu activé en même temps que je clique dessus. Ma question est: comment puis-je attendre la fin d'une coroutine et seulement après l'achèvement de la coroutine exécutez println(res) et button.isEnabled = true .

private var res: String = ""

private suspend fun test(): String {
    delay(3000) // delay - just replacement for long loop
    return "Some String" // String received after loop
}

fun onClick(view: View) {
    res = ""
    button.isEnabled = false
    GlobalScope.launch {
        res = withContext(Dispatchers.Default) {
            test()
        }
    }
    println(res) // 1. trying to get string received after loop, but not working
    button.isEnabled = true // 2. button must be enabled after loop in cycle, but it's not waiting till end of loop
}


0 commentaires

5 Réponses :


2
votes

pourquoi vous ne déplacez pas println et button.isEnabled dans la coroutine GlobalScope.launch .

GlobalScope.launch(Dispatchers.Main) {
   val res = withContext(Dispatchers.Default) {
            test()
        }

        println(res)
        button.isEnabled = true 
}

si vous voulez que votre code s'exécute sur main thread main ajoutez Dispatchers.Main comme argument.

fun onClick(view: View) {
    res = ""
    button.isEnabled = false
    GlobalScope.launch {
        val res = withContext(Dispatchers.Default) {
            test()
        }

        println(res)
        button.isEnabled = true 
    }
}

maintenant println et button.isEnabled s'exécutent sur main thread main et test() fun s'exécute sur Default qui en réalité est un thread de travail.


6 commentaires

Merci, mais lorsque j'essaie de le faire, j'ai une erreur -> java.lang.IllegalStateException: Le module avec le répartiteur principal n'a pas pu s'initialiser


@Shkum je ne sais pas que votre projet est un projet Android ou pas? pour android, vous devez ajouter cette dépendance ou mettre à jour la dernière version: implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.0'


Merci, je l'ai fait avant. Mon projet est pour Android. Au début, j'ai fait un projet sans coroutine, mais l'application se fige pendant 5 à 10 secondes pendant la boucle. J'essaye donc d'utiliser la coroutine, mais sans succès.


Je suis désolé, revérifiez simplement, ma bibliothèque coroutine 1.0.0, je vais mettre à jour et revérifier.


Cela fonctionne après la mise à jour :), mais j'ai besoin de créer un adaptateur pour listView ensemble avec "button.isEnabled = true" et je ne peux pas le créer à l'intérieur de coroutine.


Oui, vous pouvez, utilisez simplement withContext(Dispatchers.Main) pour tout ce qui doit s'exécuter sur le thread principal. Notez que vous ne devez pas mettre à jour directement l'interface utilisateur à partir du code de logique métier, essayez de structurer votre application en suivant la conception de l'architecture propre.



0
votes

Utilisez la Job.join(): Unit pour attendre la fin d'une coroutine avant de continuer avec le thread actuel:

//launch coroutine
var result = ""
val request = launch {
    delay(500)
    result = "Hello, world!"
}

//join coroutine with current thread
request.join()

//print "Hello, world!"
println(result)


1 commentaires

Merci, mais quand j'essaye de le faire, studio dit que join () ne peut être appelé que depuis une autre coroutine.



0
votes

launch est pour les situations où vous ne vous souciez pas du résultat en dehors de la coroutine. Pour récupérer le résultat d'une coroutine, utilisez async .

val res = GlobalScope.async(Dispatchers.Default) { test() }.await()

Remarque: évitez d'utiliser GlobalScope , fournissez plutôt votre propre CoroutineScope .


2 commentaires

Merci, mais quand j'essaye de le faire, il dit: <Référence non résolue. Aucun des candidats suivants n'est applicable, en raison de l'inadéquation du type de récepteur.> Si je le mets dans GlobalScope.launch et que je démarre - alors rien ne se passe. Je viens de commencer à apprendre les Coroutines, donc je suis très nouveau, je ne sais pas comment utiliser mon propre CoroutineScope.


Ouais désolé, j'ai raté GlobalScope car je ne l'utilise presque jamais.



9
votes

La principale chose à comprendre ici est que le code dans coroutine est par défaut exécuté séquentiellement. Ie coroutine est exécuté de manière asynchrone par rapport au code "frère", mais le code dans coroutine s'exécute de manière synchrone par défaut.

Par exemple:

GlobalScope.launch (Dispatchers.IO) {

   //do some background work
   ...
   withContext (Dispatchers.Main) { 
       //update the UI 
       button.isEnabled=true  
       ...
     }
}

Corroutine A exécutera async par rapport à un code supplémentaire mais doSometingA2 sera exécuté après que doSomethingA1 soit terminé.

Cela signifie que dans une coroutine, chaque morceau de code suivant sera exécuté après le précédent. Donc, quoi que vous vouliez exécuter lorsque la coroutine est terminée, il vous suffit de la mettre à la fin de cette coroutine et de déclarer le contexte ( withContext ) dans lequel vous voulez l'exécuter.

L'exception est bien sûr si vous démarrez un autre morceau de code asynchrone dans coroutine (comme une autre coroutine).

EDIT: Si vous avez besoin de mettre à jour l'interface utilisateur à partir de la coroutine, vous devez l'exécuter sur le contexte principal, c'est-à-dire que vous aurez quelque chose comme ceci:

fun DoSometing () { 

coroutineA {
doSomethingA1()
doSomethingA2()
}

some additional code 

}


2 commentaires

Merci, je l'ai essayé, et c'est une bonne solution, mais je ne peux pas faire à l'intérieur de coroutine - button.isEnabled = true - sa cause Erreur. Et aussi à l'intérieur de la boucle, j'obtiens un tableau, et ce tableau doit être ajouté à listView par adaptateur, et la création d'un adaptateur à l'intérieur de coroutine provoquant également une erreur :(, sans activer le bouton et l'adaptateur pour listView, votre conseil (explication) fonctionne très bien Dans mon exemple, println (res) a été remplacé par la création d'un adaptateur pour listView. Voici mon exemple très simplifié pour une compréhension facile de j'essaie d'obtenir.


J'ai édité ma réponse pour illustrer comment vous pouvez le faire à partir de la coroutine. Vous obtenez une erreur car vous essayez de mettre à jour l'interface utilisateur à partir du fil d'arrière-plan.



6
votes

Vous pouvez essayer quelque chose comme ceci:

fun onClick(view: View) {
    res = ""
    button.isEnabled = false
    GlobalScope.launch(Dispatchers.Main){ // launches coroutine in main thread
         updateUi()
    }
}

suspend fun updateUi(){
    val value = GlobalScope.async { // creates worker thread
       res = withContext(Dispatchers.Default) {
          test()
       }
    }
    println(value.await()) //waits for workerthread to finish
    button.isEnabled = true //runs on ui thread as calling function is on Dispatchers.main
}

Wait attendra que la coroutine se termine, puis exécutera le code en dessous

suspend fun saveInDb() {
    val value = GlobalScope.async {
       delay(1000)
       println("thread running on [${Thread.currentThread().name}]")
       10
    }
    println("value =  ${value.await()} thread running on [${Thread.currentThread().name}]")
} 


5 commentaires

Parfait, ça marche. C'est exactement ce dont j'ai besoin. Merci.


veuillez également lire ceci medium.com/@elizarov/...


Pouvez-vous s'il vous plaît expliquer pourquoi j'obtiens l'erreur "Seul le thread d'origine qui a créé une hiérarchie de vues peut toucher sa vue" lorsque j'essaie de modifier textView à l'intérieur de "suspend fun test ()" et comment le faire correctement? :)


suspend fun test () s'exécute sur le thread d'arrière-plan. Dans Android, seul le fil principal peut toucher / mettre à jour les vues


Je le connais. Je viens de mettre textView.text = "quelque chose" à l'intérieur de GlobalScope.launch (Dispatchers.Main) et ça marche. Merci beaucoup pour votre aide.