Quelle est la différence entre launch / join et async / await dans les coroutines Kotlin


Réponses:


232
  • launchest utilisé pour tirer et oublier la coroutine . C'est comme démarrer un nouveau fil. Si le code à l'intérieur de se launchtermine avec une exception, il est traité comme une exception non interceptée dans un thread - généralement imprimé sur stderr dans les applications JVM principales et plante les applications Android. joinest utilisé pour attendre la fin de la coroutine lancée et il ne propage pas son exception. Cependant, une coroutine enfant en panne annule également son parent avec l'exception correspondante.

  • asyncest utilisé pour démarrer une coroutine qui calcule un résultat . Le résultat est représenté par une instance de Deferredet vous devez l' utiliser await. Une exception non interceptée à l'intérieur du asynccode est stockée dans le résultat Deferredet n'est livrée nulle part ailleurs, elle sera supprimée silencieusement à moins qu'elle ne soit traitée. Vous NE DEVEZ PAS oublier la coroutine que vous avez commencée avec async .


1
Async est-il le bon constructeur de coroutine pour les appels réseau sous Android?
Faraaz

Le bon constructeur de coroutine dépend de ce que vous essayez d'accomplir
Roman Elizarov

9
Pouvez-vous élaborer sur "Vous NE DEVEZ PAS oublier la coroutine que vous avez commencée avec async"? Y a-t-il des pièges auxquels on ne s'attendrait pas, par exemple?
Luis

2
"Une exception non interceptée dans le code asynchrone est stockée dans le Deferred résultant et n'est livrée nulle part ailleurs, elle sera supprimée silencieusement à moins qu'elle ne soit traitée."
Roman Elizarov

9
Si vous oubliez le résultat de l'async, il se terminera et sera ramassé. Cependant, s'il plante à cause d'un bogue dans votre code, vous ne saurez jamais cela. C'est pourquoi.
Roman Elizarov

77

Je trouve ce guide https://github.com/Kotlin/kotlinx.coroutines/blob/master/coroutines-guide.md utile. Je citerai les parties essentielles

🦄 coroutine

Les coroutines sont essentiellement des threads légers.

Vous pouvez donc considérer la coroutine comme quelque chose qui gère les threads de manière très efficace.

🐤 lancement

fun main(args: Array<String>) {
    launch { // launch new coroutine in background and continue
        delay(1000L) // non-blocking delay for 1 second (default time unit is ms)
        println("World!") // print after delay
    }
    println("Hello,") // main thread continues while coroutine is delayed
    Thread.sleep(2000L) // block main thread for 2 seconds to keep JVM alive
}

Alors launchdémarre un thread d'arrière-plan, fait quelque chose et renvoie un jeton immédiatement comme Job. Vous pouvez appeler joincela Jobpour bloquer jusqu'à ce que ce launchfil soit terminé

fun main(args: Array<String>) = runBlocking<Unit> {
    val job = launch { // launch new coroutine and keep a reference to its Job
        delay(1000L)
        println("World!")
    }
    println("Hello,")
    job.join() // wait until child coroutine completes
}

🦆 asynchrone

Conceptuellement, async est comme le lancement. Il démarre une coroutine distincte qui est un thread léger qui fonctionne simultanément avec toutes les autres coroutines. La différence est que le lancement renvoie un Job et ne porte aucune valeur résultante, tandis que async renvoie un Deferred - un futur léger et non bloquant qui représente une promesse de fournir un résultat plus tard.

Alors asyncdémarre un thread d'arrière-plan, fait quelque chose et renvoie un jeton immédiatement comme Deferred.

fun main(args: Array<String>) = runBlocking<Unit> {
    val time = measureTimeMillis {
        val one = async { doSomethingUsefulOne() }
        val two = async { doSomethingUsefulTwo() }
        println("The answer is ${one.await() + two.await()}")
    }
    println("Completed in $time ms")
}

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.

Alors Deferredest en fait un Job. Voir https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/-deferred/index.html

interface Deferred<out T> : Job (source)

🦋 async est impatient par défaut

Il existe une option de paresse pour asynchroniser à l'aide d'un paramètre de démarrage facultatif avec une valeur de CoroutineStart.LAZY. Il démarre la coroutine uniquement lorsque son résultat est requis par certains wait ou si une fonction de démarrage est appelée.


11

launchet asyncsont utilisés pour démarrer de nouvelles coroutines. Mais, ils les exécutent de manière différente.

Je voudrais montrer un exemple très basique qui vous aidera à comprendre la différence très facilement

  1. lancement
    class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        btnCount.setOnClickListener {
            pgBar.visibility = View.VISIBLE
            CoroutineScope(Dispatchers.Main).launch {
                val currentMillis = System.currentTimeMillis()
                val retVal1 = downloadTask1()
                val retVal2 = downloadTask2()
                val retVal3 = downloadTask3()
                Toast.makeText(applicationContext, "All tasks downloaded! ${retVal1}, ${retVal2}, ${retVal3} in ${(System.currentTimeMillis() - currentMillis)/1000} seconds", Toast.LENGTH_LONG).show();
                pgBar.visibility = View.GONE
            }
        }

    // Task 1 will take 5 seconds to complete download
    private suspend fun downloadTask1() : String {
        kotlinx.coroutines.delay(5000);
        return "Complete";
    }

    // Task 1 will take 8 seconds to complete download    
    private suspend fun downloadTask2() : Int {
        kotlinx.coroutines.delay(8000);
        return 100;
    }

    // Task 1 will take 5 seconds to complete download
    private suspend fun downloadTask3() : Float {
        kotlinx.coroutines.delay(5000);
        return 4.0f;
    }
}

Dans cet exemple, mon code télécharge 3 données en cliquant sur le btnCountbouton et affiche pgBarla barre de progression jusqu'à ce que tout le téléchargement soit terminé. Il y a 3 suspendfonctions downloadTask1(), downloadTask2()et downloadTask3()qui télécharge des données. Pour le simuler, j'ai utilisé delay()dans ces fonctions. Ces fonctions attendent 5 seconds, 8 secondset 5 secondsrespectivement.

Comme nous l'avons utilisé launchpour démarrer ces fonctions de suspension, launchles exécutera séquentiellement (une par une) . Cela signifie que, downloadTask2()commencerait une fois downloadTask1()terminé et downloadTask3()ne commencerait qu'une fois downloadTask2()terminé.

Comme dans la capture d'écran de sortie Toast, le temps total d'exécution pour terminer les 3 téléchargements conduirait à 5 secondes + 8 secondes + 5 secondes = 18 secondes aveclaunch

Exemple de lancement

  1. asynchrone

Comme nous l'avons vu, cela launchpermet l'exécution sequentiallydes 3 tâches. Le temps était venu de terminer toutes les tâches 18 seconds.

Si ces tâches sont indépendantes et si elles n'ont pas besoin du résultat de calcul d'une autre tâche, nous pouvons les faire fonctionner concurrently. Ils commenceraient en même temps et fonctionneraient simultanément en arrière-plan. Cela peut être fait avec async.

asyncrenvoie une instance de Deffered<T>type, où Test le type de données renvoyées par notre fonction de suspension. Par exemple,

  • downloadTask1()retournerait Deferred<String>car String est le type de retour de la fonction
  • downloadTask2()retournerait Deferred<Int>car Int est le type de retour de la fonction
  • downloadTask3()retournerait Deferred<Float>car Float est le type de retour de la fonction

Nous pouvons utiliser l'objet de retour de asyncde type Deferred<T>pour obtenir la valeur retournée dans Ttype. Cela peut être fait avec un await()appel. Vérifiez le code ci-dessous par exemple

        btnCount.setOnClickListener {
        pgBar.visibility = View.VISIBLE

        CoroutineScope(Dispatchers.Main).launch {
            val currentMillis = System.currentTimeMillis()
            val retVal1 = async(Dispatchers.IO) { downloadTask1() }
            val retVal2 = async(Dispatchers.IO) { downloadTask2() }
            val retVal3 = async(Dispatchers.IO) { downloadTask3() }

            Toast.makeText(applicationContext, "All tasks downloaded! ${retVal1.await()}, ${retVal2.await()}, ${retVal3.await()} in ${(System.currentTimeMillis() - currentMillis)/1000} seconds", Toast.LENGTH_LONG).show();
            pgBar.visibility = View.GONE
        }

De cette façon, nous avons lancé les 3 tâches simultanément. Donc, mon temps d'exécution total pour terminer serait seulement 8 secondsce qui est le temps downloadTask2()car c'est la plus grande des 3 tâches. Vous pouvez le voir dans la capture d'écran suivante dansToast message

attendre l'exemple


1
Merci de mentionner que launchc'est pour les funs séquentiels , tandis que asyncpour les simultanés
Akbolat SSS

Vous avez utilisé le lancement une fois pour toutes les tâches et l'asynchrone pour chacune. Peut-être que c'est plus rapide parce que chacun a été lancé dans une autre coroutine et n'attend personne? C'est une comparaison incorrecte. Les performances sont généralement les mêmes. Une différence clé est que le lancement démarre toujours une nouvelle coroutine au lieu d'une asynchrone qui sépare celle du propriétaire. Un autre facteur est que si l'une des tâches asynchrones échoue pour une raison, la coroutine parente échouera non plus. C'est pourquoi async n'est pas aussi populaire que le lancement.
p2lem8dev

1
Cette réponse n'est pas juste, comparant les fonctions async avec suspendre directement au lieu de lancer. Au lieu d'appeler directement la fonction de suspension dans l'exemple, si vous appelez launch (Dispatchers.IO) {downloadTask1 ()}, vous verrez que les deux sont exécutés simultanément, pas séquentiellement , vous ne pourrez pas obtenir de sorties mais vous verrez que c'est pas séquentiel. De plus, si vous ne concaténez pas deferred.await () et appelez deferred.await () séparément, vous verrez que async est séquentiel.
thrace

2
-1 c'est tout simplement faux. Les deux launchet asyncvont démarrer de nouvelles coroutines. Vous comparez une seule coroutine sans enfants à une seule coroutine avec 3 enfants. Vous pouvez remplacer chacune des asyncinvocations par launchet absolument rien ne changerait en ce qui concerne la concurrence.
Moira

Le bruit étranger dans cette réponse ajoute une complexité qui est en dehors du sujet de co-routine.
truthadjustr

6
  1. les deux constructeurs de coroutine, à savoir le lancement et l'asynchrone, sont essentiellement des lambdas avec un récepteur de type CoroutineScope, ce qui signifie que leur bloc interne est compilé en tant que fonction de suspension, par conséquent, ils s'exécutent tous les deux en mode asynchrone ET ils exécuteront tous les deux leur bloc séquentiellement.

  2. La différence entre lancer et asynchrone est qu'ils offrent deux possibilités différentes. Le générateur de lancement renvoie un Job mais la fonction async renverra un objet Deferred. Vous pouvez utiliser launch pour exécuter un bloc dont vous n'attendez pas de valeur renvoyée, c'est-à-dire écrire dans une base de données ou enregistrer un fichier ou traiter quelque chose qui est simplement appelé pour son effet secondaire. D'autre part, async qui retourne un Deferred comme je l'ai dit précédemment renvoie une valeur utile de l'exécution de son bloc, un objet qui enveloppe vos données, vous pouvez donc l'utiliser principalement pour son résultat mais éventuellement aussi pour son effet secondaire. NB: vous pouvez supprimer le différé et récupérer sa valeur à l'aide de la fonction wait, qui bloquera l'exécution de vos instructions jusqu'à ce qu'une valeur soit renvoyée ou qu'une exception soit levée!

  3. les deux coroutine builder (lancement et asynchrone) sont annulables.

  4. quoi de plus?: oui au lancement si une exception est levée dans son bloc, la coroutine est automatiquement annulée et les exceptions sont livrées. D'autre part, si cela se produit avec async, l'exception n'est pas propagée davantage et doit être interceptée / gérée dans l'objet Deferred renvoyé.

  5. plus sur coroutines https://kotlinlang.org/docs/tutorials/coroutines/coroutines-basic-jvm.html https://www.codementor.io/blog/kotlin-coroutines-6n53p8cbn1


1
Merci pour ce commentaire. Il a rassemblé tous les points du fil. J'ajouterais que tous les lancements ne sont pas annulés, par exemple Atomic ne peut jamais être annulé.
p2lem8dev

4

lancer renvoie un travail

async renvoie un résultat (tâche différée)

lancer avec join est utilisé pour attendre que le travail soit terminé.

async est utilisé pour calculer certains résultats. Il crée une coroutine et renvoie son résultat futur en tant qu'implémentation de Deferred. La coroutine en cours d'exécution est annulée lorsque le différé résultant est annulé.

Considérez une méthode asynchrone qui renvoie une valeur de chaîne. Si la méthode async est utilisée sans attendre, elle retournera une chaîne Deferred mais si await est utilisé, vous obtiendrez une chaîne comme résultat

La principale différence entre async et launch. Deferred renvoie une valeur particulière de type T après la fin de l'exécution de votre Coroutine, contrairement à Job.


0

Async vs Launch Async vs Launch Diff Image

lancer / asynchrone aucun résultat

  • Utilisez quand vous n'avez pas besoin de résultat,
  • Ne bloquez pas le code où il est appelé,
  • Exécuter en parallèle

asynchrone pour résultat

  • Lorsque vous devez attendre le résultat et que vous pouvez fonctionner en parallèle pour plus d'efficacité
  • Bloquer le code où est appelé
  • courir en parallèle
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.