Differenza di Kotlin tra CoroutineScope e withContext

Sep 15 2020

Per cambiare il thread in una funzione uso CoroutineScope o withContext. Non so quale sia la differenza, ma con CourineScope posso anche usare un handler.

Esempi:

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

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

Chiamo questa funzione da una coroutine che funziona su thread in background (IO). È più appropriato dell'altro?

Risposte

1 MarkoTopolnik Sep 17 2020 at 16:17

Questi due sono in realtà radicalmente diversi e ti capita di avere un caso d'uso in cui non si verifica la differenza:

CoroutineScope(Main).launch(handler){

Questo avvia una coroutine simultanea che va avanti indipendentemente.

withContext(Main){

Questa è una funzione che si completa solo quando il codice al suo interno termina e restituisce il suo risultato. Questo è il modo in cui dovresti farlo.

Il primo approccio, con CoroutineScope, presenta un'altra lacuna in quanto aggira la concorrenza strutturata. Si crea un ambito coroutine ad-hoc che non ha un genitore e quindi non verrà automaticamente ripulito se il completamento richiede più tempo e la GUI viene eliminata (l'utente esce dall'attività corrente).

In realtà non dovresti mai usare l' CoroutineScope(Main)idioma, non penso che ci sia un solo caso in cui sarebbe appropriato. Se si desidera esplicitamente evitare la concorrenza strutturata, è ancora meglio e più pulito scrivere

GlobalScope.launch(Main + handler) {

e ha più o meno lo stesso effetto.

Se vuoi una coroutine simultanea che si adatti alla concorrenza strutturata, usa

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

Nota: ho rimosso l' handlerargomento, una coroutine figlia lo ignora perché inoltra eventuali errori alla coroutine genitrice, che è esattamente ciò che desideri. La coroutine genitore dovrebbe avere un gestore di eccezioni installato.

USMANosman Sep 15 2020 at 22:20

Tecnicamente entrambi sono uguali, ma quando si tratta di casi d'uso entrambi sono diversi e hanno un grande impatto sui diversi casi d'uso, quindi fai attenzione quando li usi
Coroutine Scope:
CoroutineScope è un punto di partenza di Coroutine. CoroutineScope può avere più di una coroutine al suo interno, il che rende la coroutine gerarchica. Pensiamo, il genitore ha più di un figlio. Think CoroutineScopeè un genitore e questo genitore può avere più di un figlio che sono anche coroutine. Questi bambini sono conosciuti comejob

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

vedi quel bambino Uno e bambino Due? perché ne abbiamo bisogno? poiché non possiamo annullare direttamente la coroutine, non esiste un modo in cui la coroutine può essere annullata direttamente, o la coroutine viene completata o viene fallita. E se volessimo cancellarlo? in questi casi abbiamo bisogno job. Ma cosa da notare qui questi lavori childrensono totalmente associati al genitore. E il genitore è (IO) ei bambini sono (Principale), questo genitore è avviato in IO Disptacher ma quando si tratta di quei bambini passeranno a (Principale) e faranno le loro cose, ma il genitore sarà ancora al cambio (IO) il Dispatcher dei bambini non effettuerà il genitore.
Ma cosa succede se succede qualcosa di sbagliato a uno dei bambini, in quel caso guarderemo questo vertice:
https://www.youtube.com/watch?v=w0kfnydnFWI
Questo summit sull'eccezione e la cancellazione della coroutine. guardalo, è incredibile ...

withContext:
cosa è withContext?
withContextdovrebbe essere all'interno di qualsiasi Coroutineo suspend funperché withContextessa stessa è una funzione di sospensione.
withContextserve per cambiare il contesto in una situazione diversa
ma come?


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

guarda il codice, stiamo recuperando i dati in modo asincrono dalla rete perché non vogliamo bloccarlo MainThreade quindi cambiamo il contesto, perché? causa non possiamo aggiornare UI roba correlata in IoDispatcher questo è abbiamo cambiare il contesto maincon withContext(main){}e aggiornare l'interfaccia utente.
e ci sono altri casi d'uso come liveData, stiamo recuperando il valore usando il retrofit usando IoDispatcher, quindi nel passaggio successivo dobbiamo impostarlo su liveData usando withContext(main){}perché non possiamo osservare il valore di liveData nel thread in background.
sì, spero che questo aiuti. commenta se c'è qualche domanda.

NicolaGallazzi Sep 16 2020 at 16:28

Dalla articolo Antonio Leiva su coroutine:

Il contesto coroutine è un insieme di regole e configurazioni che definiscono come verrà eseguita la coroutine

withContextè una funzione che permette di cambiare facilmente la contextfunzione di sospensione, in modo da essere sicuri che quella funzione venga eseguita in un particolare thread (es. Thread da IO pool). Per fare ciò puoi forzare una funzione di sospensione ad eseguire il suo corpo all'interno di un particolare pool di thread, ad esempio:

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

In questo modo, anche se stai chiamando questa funzione di sospensione da un ambito dell'interfaccia utente (MainScope), sei sicuro al 100% che la funzione di sospensione viene eseguita in un thread di lavoro e puoi aggiornare l'interfaccia utente con il risultato restituito nel thread principale, ad esempio:

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

Per riassumere, utilizzando withContextè possibile rendere la funzione di sospensione "Main Safe" .

La differenza tra scopee contextè fondamentalmente lo scopo previsto. Per avviare una coroutine normalmente si utilizza launchcoroutine builder, definito come una funzione di estensione su CoroutineScope.

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

Il contesto specificato come parametro sull'ambito coroutine viene unito all'ambito coroutine dall'operatore plus e ha la precedenza sul contesto "predefinito" specificato dall'ambito coroutine. In questo modo puoi eseguire il codice in un contesto "genitore". Per approfondire ti suggerisco questo articolo di Roman Elizarov (Team Lead per le librerie Kotlin @JetBrains).