Coroutines Kotlin et résumé d'utilisation dans Android (comparaison de cinq collections, séquences, flux et RxJava (ci-dessous))

Insérez la description de l'image ici

aplatissement du flux

Supposons maintenant qu'il existe un tel appel:

fun requestFlow(i: Int): Flow<String> = flow {
    emit("$i: First") 
    delay(500) // wait 500 ms
    emit("$i: Second")    
}

(1..3).asFlow().map { requestFlow(it) }

Cette fois - ci obtenir un tel flux Flow<Flow<String>>, quand on a écrasé le processus final est de se séparer en une Flow<String>collection de séquences, et à cet effet a aplatir et flatMap opérateurs. Cependant, en raison de la nature asynchrone de l'écoulement, ils nécessitent différents modes d'aplatissement, il existe donc une série d'opérateurs d'aplatissement sur l'écoulement.

flatMapConcat

Le mode de concaténation est implémenté par les opérateurs flatMapConcat et flattenConcat. Ce sont les analogues les plus directs des opérateurs de séquence correspondants. Ils attendent la fin du processus interne, puis commencent à collecter l'exemple suivant, comme illustré dans l'exemple suivant:

val startTime = System.currentTimeMillis() // remember the start time 
(1..3).asFlow().onEach { delay(100) } // a number every 100 ms 
    .flatMapConcat { requestFlow(it) }                                                                           
    .collect { value -> // collect and print 
        println("$value at ${System.currentTimeMillis() - startTime} ms from start") 
    } 

La nature de l'ordre de flatMapConcat peut être clairement vue à partir de la sortie:

1: Première à 121 ms du début
1: Deuxième à 622 ms du début
2: Première à 727 ms du début
2: Deuxième à 1227 ms du début
3: Première à 1328 ms du début
3: Deuxième à 1829 ms du début

flatMapMerge

Un autre mode d'aplatissement consiste à collecter tous les flux entrants en même temps et à fusionner leurs valeurs en un seul flux afin que les valeurs soient envoyées dès que possible. Il est implémenté par les opérateurs flatMapMerge et flattenMerge. Ils acceptent tous les deux un paramètre de concurrence facultatif, ce qui limite le nombre de flux simultanés collectés simultanément (par défaut, il est égal à 16).

val startTime = System.currentTimeMillis() // remember the start time 
(1..3).asFlow().onEach { delay(100) } // a number every 100 ms 
    .flatMapMerge { requestFlow(it) }                                                                           
    .collect { value -> // collect and print 
        println("$value at ${System.currentTimeMillis() - startTime} ms from start") 
    } 

La nature simultanée de flatMapMerge est évidente:

1: Premier à 136 ms du début
2: Premier à 231 ms du début
3: Premier à 333 ms du début
1: Deuxième à 639 ms du début
2: Deuxième à 732 ms du début
3: Deuxième à 833 ms du début

Veuillez noter que flatMapMerge appelle ses blocs de code séquentiellement (dans cet exemple, * {requestFlow (it)} ), mais en même temps recueille le flux de résultats simultanément, ce qui équivaut à exécuter la carte séquencée {requestFlow (it)} d'abord , puis le résultat Appelez flattenMerge *

flatMapLatest

Comme dans l'introduction précédente à l'opérateur collectLatest, la logique de l'opérateur flatMapLatest lors de l'aplatissement du flux est la même. Autrement dit, chaque fois que le flux émet une nouvelle valeur, si le collecteur actuel n'a pas été traité, l'exécution est annulée et l'opérateur nouvellement lancé est exécuté directement. La valeur, c'est-à-dire:

val startTime = System.currentTimeMillis() // remember the start time 
(1..3).asFlow().onEach { delay(100) } // a number every 100 ms 
    .flatMapLatest { requestFlow(it) }                                                                           
    .collect { value -> // collect and print 
        println("$value at ${System.currentTimeMillis() - startTime} ms from start") 
    } 

Le principe de fonctionnement de l'opérateur flatMapLatest est indiqué dans le journal suivant:

1: Premier à 142 ms du début
2: Premier à 322 ms du début
3: Premier à 425 ms du début
3: Deuxième à 931 ms du début


Exceptions de flux

Lorsque le code de l'émetteur émetteur ou opérateur lève une exception, le flux est également terminé avec une exception. Il existe plusieurs façons de traiter ces exceptions.

Utilisez try-catch pour intercepter l'exception du collecteur

fun foo(): Flow<Int> = flow {
    for (i in 1..3) {
        println("Emitting $i")
        emit(i) // emit next value
    }
}

fun main() = runBlocking<Unit> {
    try {
        foo().collect { value ->         
            println(value)
            check(value <= 1) { "Collected $value" }
        }
    } catch (e: Throwable) {
        println("Caught $e")
    } 
}  

Le collecteur arrêtera le traitement du flux après qu'une exception se soit produite et la sortie du journal après l'exception sera capturée comme suit:

Emitting 1
1
Emitting 2
2
Caught java.lang.IllegalStateException: Collected 2

Toute anomalie de débit peut être détectée

Grâce à try-catch, outre les exceptions levées par le collecteur, les exceptions générées par l'opérateur intermédiaire et l'opérateur de terminal peuvent être capturées. Le code suivant génère une exception dans l'opérateur intermédiaire:

fun foo(): Flow<String> = 
    flow {
        for (i in 1..3) {
            println("Emitting $i")
            emit(i) // emit next value
        }
    }
    .map { value ->
        check(value <= 1) { "Crashed on $value" }                 
        "string $value"
    }

fun main() = runBlocking<Unit> {
    try {
        foo().collect { value -> println(value) }
    } catch (e: Throwable) {
        println("Caught $e")
    } 
}   

L'exception est interceptée et le processus du collecteur se termine, le journal est le suivant:

Emitting 1
string 1
Emitting 2
Caught java.lang.IllegalStateException: Crashed on 2

Transparence exceptionnelle

Les flux doivent garantir une transparence exceptionnelle. En d'autres termes, vous ne pouvez pas simplement encapsuler l'intégralité de la logique d'émission et de collecte dans le try-catch comme dans le code ci-dessus. Vous devez gérer la logique de gestion des exceptions séparément pour la partie d'émission de flux, afin que le collecteur n'ait plus à se soucier de ce qui s'est passé avant le traitement. Conditions anormales.

Cela utilise l'opérateur intermédiaire catch pour effectuer un traitement de capture d'exception sur la partie d'émission du flux, et différents traitements peuvent être effectués en fonction de l'exception capturée dans le bloc de capture, y compris les méthodes de traitement suivantes:

  • Vous pouvez à nouveau lever une exception en lançant
  • Peut émettre de la valeur d'émission anormale est convertie en
  • Peut ignorer les exceptions, imprimer les journaux ou utiliser une autre logique de traitement de code

L'exception est convertie en valeur d'émission comme suit:

foo()
    .catch { e -> emit("Caught $e") } // emit on exception
    .collect { value -> println(value) }

Le journal est identique à l'utilisation de try-catch pour encapsuler tout le code:

Emitting 1
string 1
Emitting 2
Caught java.lang.IllegalStateException: Crashed on 2

L'opérateur catch garantit la transparence d'exception du flux, mais il n'est valide que pour le flux de flux au-dessus du bloc catch. Il ne peut pas gérer l'exception générée par le processus de collecte. Le code suivant génère une exception dans la collecte et lève une exception:

fun foo(): Flow<Int> = flow {
    for (i in 1..3) {
        println("Emitting $i")
        emit(i)
    }
}

fun main() = runBlocking<Unit> {
    foo()
        .catch { e -> println("Caught $e") } // does not catch downstream exceptions
        .collect { value ->
            check(value <= 1) { "Collected $value" }                 
            println(value) 
        }
} 

Afin de remplacer l'intégralité du try-catch imbriqué du flux, c'est-à-dire d'assurer la transparence de l'exception du flux et de permettre la capture et le traitement de l'exception dans le processus de collecte, la logique du processus de collecte peut être transférée vers la capture avant le traitement, par exemple via onEach. L'opérateur traite les valeurs de séquence asynchrones dans le flux avant de capturer:

foo()
    .onEach { value ->
        check(value <= 1) { "Collected $value" }                 
        println(value) 
    }
    .catch { e -> println("Caught $e") }
    .collect()

La sortie du journal sera la même que ci-dessus, et l'exception sera interceptée:

Emitting 1
1
Emitting 2
Caught java.lang.IllegalStateException: Collected 2

Achèvement du flux

Le flux peut avoir besoin d'effectuer une opération lorsqu'il se termine normalement ou lorsqu'une exception se produit. Nous pouvons utiliser try-catch-finally pour ajouter une opération, ou nous pouvons utiliser l' opérateur onCompletion , qui a un paramètre de type Throwable nul pour déterminer le courant fin est normal ou anormal, bien sûr, cela ne peut être déterminée pour onCompletion flux avant que l'opérateur et onCompletion opérateur ne saisit pas l'exception, cela continuera de se propager vers le bas, le code suivant:

foo()
    .onCompletion { println("Done") }
    .collect { value -> println(value) }
fun foo(): Flow<Int> = flow {
    emit(1)
    throw RuntimeException()
}

fun main() = runBlocking<Unit> {
    foo()
        .onCompletion { cause -> if (cause != null) println("Flow completed exceptionally") }
        .catch { cause -> println("Caught exception") }
        .collect { value -> println(value) }
} 

Le code ci-dessus affichera le journal:

1
flux terminé exceptionnellement exception
capturée

fun foo(): Flow<Int> = (1..3).asFlow()

fun main() = runBlocking<Unit> {
    foo()
        .onCompletion { cause -> println("Flow completed with $cause") }
        .collect { value ->
            check(value <= 1) { "Collected $value" }                 
            println(value) 
        }
}

Dans le code ci-dessus, le processus avant onCompletion n'a pas généré d'exception, donc la cause est nulle et l'exception générée dans la collecte sera toujours levée, le journal est le suivant:

1
Flux terminé avec une
exception nulle dans le thread «principal» java.lang.IllegalStateException: collecté 2


Flux de lancement

Dans le code ci-dessus, nous exécutons le flux de traitement de flux à l'intérieur d'un bloc * runBlocking {} *, c'est-à-dire dans une coroutine, ce qui fait que le traitement de flux bloquera l'exécution du code suivant, tel que:

// Imitate a flow of events
fun events(): Flow<Int> = (1..3).asFlow().onEach { delay(100) }

fun main() = runBlocking<Unit> {
    events()
        .onEach { event -> println("Event: $event") }
        .collect() // <--- Collecting the flow waits
    println("Done")
} 

Affichera le journal:

Événement: 1
Événement: 2
Événement: 3
Terminé

Si nous voulons que le code après la collecte soit exécuté en même temps, nous pouvons utiliser l' opérateur launchIn , qui place le flux du flux dans une nouvelle coroutine, afin que son code suivant puisse être exécuté immédiatement, comme suit:

fun main() = runBlocking<Unit> {
    events()
        .onEach { event -> println("Event: $event") }
        .launchIn(this) // <--- Launching the flow in a separate coroutine
    println("Done")
}   

Affichera le journal:

Terminé
Événement: 1
Événement: 2
Événement: 3

Le paramètre transmis par l'opérateur launchIn doit être un objet qui déclare CoroutineScope, tel que le CoroutineScope créé par le générateur de coroutine runBlocking. Habituellement, ce CoroutineScope est un objet avec une période de déclaration limitée, comme viewModelScope, lifecycleScope, etc., à la fin de son cycle de vie À ce moment, son traitement de flux interne se terminera également, onEach {…} .launchIn (scope) ici peut fonctionner comme addEventListener (utilisez un morceau de code pour traiter l'événement entrant en conséquence et continuez à travailler plus loin) .

L' opérateur launchIn renvoie un travail en même temps , nous pouvons donc également utiliser cancel pour annuler la coroutine de collecte du flux, ou utiliser join pour exécuter le travail .

Flux et flux réactifs

L'inspiration pour la conception de flux de flux provient des flux réactifs, tels que RxJava, mais le principal objectif du flux est d'avoir la conception la plus simple possible, d'utiliser Kotlin, un support de fonction de suspension convivial et d'adhérer à une concurrence structurée.

Bien qu'il soit différent des autres flux réactifs (tels que RxJava), le flux lui-même est également un flux réactif, vous pouvez donc utiliser la bibliothèque de conversion fournie par Kotlin pour convertir directement entre les deux, comme Kotlin coroutine et Android use summary (trois Réécrivez les rappels et les appels RxJava pour suspendre les fonctions) comme mentionné dans kotlinx-coroutines-rx2 .


参考 :
https://kotlinlang.org/docs/reference/coroutines/flow.html#suspending-functions
Développement asynchrone dans Android: RxJava Vs. Kotlin Flow

A publié 82 articles originaux · J'aime 86 · Plus de 110 000 visiteurs

Je suppose que tu aimes

Origine blog.csdn.net/unicorn97/article/details/105209834
conseillé
Classement