Différence Kotlin entre CoroutineScope et withContext

Sep 15 2020

Pour changer le fil d'une fonction, j'utilise soit CoroutineScope, soit withContext. Je ne sais pas quelle est la différence, mais avec CourineScope, je peux aussi utiliser un gestionnaire.

Exemples:

private fun removeViews(){
    CoroutineScope(Main).launch(handler){
        gridRoot.removeAllViews()
    }
}

private suspend fun removeViews(){
    withContext(Main){
        gridRoot.removeAllViews()
    }
}

J'appelle cette fonction à partir d'une coroutine qui fonctionne sur le thread d'arrière-plan (IO). Est-ce que l'un est plus approprié que l'autre?

Réponses

1 MarkoTopolnik Sep 17 2020 at 16:17

Ces deux éléments sont en fait radicalement différents et il se trouve que vous avez un cas d'utilisation où vous ne rencontrez pas la différence:

CoroutineScope(Main).launch(handler){

Cela lance une coroutine simultanée qui se poursuit indépendamment.

withContext(Main){

Il s'agit d'une fonction qui se termine uniquement lorsque le code à l'intérieur se termine et renvoie son résultat. C'est ainsi que vous devriez le faire.

La première approche, avec CoroutineScope, présente une autre lacune en ce qu'elle contourne la concurrence structurée. Vous créez une portée coroutine ad hoc qui n'a pas de parent et qui ne sera donc pas automatiquement nettoyée si cela prend plus de temps et que votre interface graphique est supprimée (l'utilisateur quitte l'activité actuelle).

En fait, vous ne devriez jamais utiliser l' CoroutineScope(Main)idiome, je ne pense pas qu'il y ait une seule instance où cela serait approprié. Si vous souhaitez explicitement éviter la concurrence structurée, il est toujours préférable et plus propre d'écrire

GlobalScope.launch(Main + handler) {

et a à peu près le même effet.

Si vous voulez une coroutine simultanée qui s'intègre dans la concurrence structurée, utilisez

fun CoroutineScope.removeViews() {
    launch {
        gridRoot.removeAllViews()
    }
}

Remarque J'ai supprimé l' handlerargument, une coroutine enfant l'ignore car elle transmet tout échec à sa coroutine parent, ce qui est exactement ce que vous voulez. La coroutine parent doit avoir un gestionnaire d'exceptions installé.

USMANosman Sep 15 2020 at 22:20

Techniquement, les deux sont identiques, mais en ce qui concerne les cas d'utilisation, les deux sont différents et ont un impact important sur les différents cas d'utilisation, alors soyez prudent lorsque vous les utilisez
Coroutine Scope:
CoroutineScope est un point de départ de Coroutine. CoroutineScope peut avoir plus d'une coroutine en lui-même, ce qui crée une hiérarchie de coroutine. Pensons, Parent a plus d'un enfant. Think CoroutineScopeest un parent et ce parent peut avoir plus d'un enfant qui sont également des coroutines. Ces enfants sont connus commejob

private val coroutineScope = CoroutineScope()
    coroutineScope(IO).launch{
    val childOne = launch(Main){}
   val childTwo = launch(Main){}
}

voir que childOne et childTwo? pourquoi nous en avons besoin? parce que nous ne pouvons pas annuler directement la coroutine, la coroutine ne peut pas être annulée directement, soit la coroutine est terminée, soit elle échoue. Mais que faire si on veut l'annuler? dans de tels cas, nous avons besoin job. Mais chose à noter ici, ces emplois childrensont totalement associés aux parents. Et le parent est (IO) et les enfants sont (Main), ce parent est démarré dans IO Disptacher mais quand il s'agit de ces enfants, ils vont passer à (Main) et faire leur truc mais le parent sera toujours en (IO) switch le répartiteur des enfants n'affectera pas les parents.
Mais que se passe-t-il si quelque chose de mal arrive à l'un des enfants, dans ce cas, nous regarderons ce sommet:
https://www.youtube.com/watch?v=w0kfnydnFWI
Ce sommet sur l'exception et l'annulation de la coroutine. regardez-le, c'est incroyable ...

withContext:
Qu'est-ce que withContext?
withContextdevrait être à l'intérieur de any Coroutineou suspend funparce que withContextlui-même est une fonction de suspension.
withContextest utilisé pour changer de contexte dans une situation différente,
mais comment?


suspend fun fetchFromNetworkAndUpdateUI() {
    withContext(IO){
        println("Some Fake data from network")
    }
    
    withContext(Main){
        //updating Ui
        //setting that Data to some TextView etc
    }
    
}

voir le code, nous récupérons les données de manière asynchrone à partir du réseau parce que nous ne voulons pas bloquer le MainThreadpuis nous changeons de contexte, pourquoi? causons nous ne pouvons pas mettre à jour des choses liées à l' interface utilisateur IoDispatcher c'est que nous avons changé le contexte mainavec withContext(main){}et mettre à jour l'interface utilisateur.
et il y a d'autres cas d'utilisation comme liveData, nous récupérons la valeur en utilisant retrofit en utilisant IoDispatcher, puis à l'étape suivante, nous devons le définir sur liveData en utilisant withContext(main){}car nous ne pouvons pas observer la valeur de liveData dans le thread d'arrière-plan.
ouais, j'espère que cela aide. commentez s'il y a des questions.

NicolaGallazzi Sep 16 2020 at 16:28

Extrait de l' article d'Antonio Leiva sur les coroutines:

Le contexte coroutine est un ensemble de règles et de configurations qui définissent comment la coroutine sera exécutée

withContextest une fonction qui vous permet de changer facilement le contextd'une fonction de suspension, afin d'être sûr que cette fonction est exécutée dans un thread particulier (par exemple Thread du pool IO). Pour ce faire, vous pouvez forcer une fonction de suspension à exécuter son corps dans un pool de threads particulier, par exemple:

suspend fun getAuthenticationStatus(): AuthenticationStatus = withContext(Dispatchers.IO) {
    when (val result = repository.getAuthenticationStatus()) {
        is Result.Success -> result.data
        is Result.Error -> AuthenticationStatus.Unauthorized
    }
}

De cette façon, même si vous appelez cette fonction de suspension à partir d'une étendue d'interface utilisateur (MainScope), vous êtes sûr à 100% que la fonction de suspension est exécutée dans un thread de travail et vous pouvez mettre à jour l'interface utilisateur avec le résultat renvoyé dans le thread principal, tel que:

MainScope().launch {
            userIdentityVM.getAuthenticationStatus().run {
                when (this) {
                    is AuthenticationStatus.Authenticated -> {
                       // do something
                    }
                    is AuthenticationStatus.Unauthorized -> {
                       // do something else
                    }
                }
            }
        }

Pour résumer, en utilisant withContextvous pouvez rendre votre fonction de suspension "Main Safe" .

La différence entre scopeet contextest essentiellement l'objectif visé. Pour lancer une coroutine, vous utilisez normalement le launchgénérateur de coroutine, défini comme une fonction d'extension sur CoroutineScope.

fun CoroutineScope.launch(
    context: CoroutineContext = EmptyCoroutineContext,
    // ...
): Job

Le contexte spécifié comme paramètre sur la portée coroutine est fusionné avec la portée coroutine par l'opérateur plus et a la priorité sur le contexte "par défaut" spécifié par la portée coroutine. De cette façon, vous pouvez exécuter le code dans un contexte "parent". Pour aller plus loin, je vous suggère cet article de Roman Elizarov (chef d'équipe pour les bibliothèques Kotlin @JetBrains).