Qual è la differenza tra launch / join e async / waitit nelle coroutine di Kotlin


Risposte:


232
  • launchè usato per sparare e dimenticare la coroutine . È come iniziare una nuova discussione. Se il codice all'interno di launchtermina con un'eccezione, viene trattato come un'eccezione non rilevata in un thread, di solito stampato su standard nelle applicazioni JVM back-end e arresta in modo anomalo le applicazioni Android. joinviene utilizzato per attendere il completamento della routine lanciata e non propaga la sua eccezione. Tuttavia, una coroutine figlio bloccata annulla anche il suo genitore con la corrispondente eccezione.

  • asyncviene utilizzato per avviare una routine che calcola alcuni risultati . Il risultato è rappresentato da un'istanza di Deferreded è necessario utilizzarlo await. Un'eccezione non rilevata all'interno del asynccodice viene archiviata all'interno del codice risultante Deferrede non viene recapitata altrove, verrà eliminata silenziosamente a meno che non venga elaborata. NON DEVI dimenticare la routine che hai iniziato con asincrono .


1
Async è il costruttore di coroutine giusto per le chiamate di rete in Android?
Faraaz,

Il giusto costruttore di coroutine dipende da ciò che stai cercando di realizzare
Roman Elizarov,

9
Puoi approfondire "NON DEVI dimenticare la coroutine che hai iniziato con asincrono"? Ci sono dei gotcha che non ci si potrebbe aspettare per esempio?
Luis

2
"Un'eccezione non rilevata all'interno del codice asincrono viene archiviata all'interno del Deferred risultante e non viene recapitata altrove, verrà eliminata automaticamente se non elaborata."
Roman Elizarov,

9
Se si dimentica il risultato di asincrono di quello che finirà e sarà spazzatura raccolta. Tuttavia, se si blocca a causa di un bug nel tuo codice, non lo saprai mai. È per questo.
Roman Elizarov,

77

Trovo che questa guida https://github.com/Kotlin/kotlinx.coroutines/blob/master/coroutines-guide.md sia utile. Citerò le parti essenziali

🦄 coroutine

In sostanza, le coroutine sono fili leggeri.

Quindi puoi pensare al coroutine come qualcosa che gestisce il thread in un modo molto efficiente.

🐤 lancio

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
}

Quindi launchavvia un thread in background, fa qualcosa e restituisce immediatamente un token come Job. È possibile chiamare joinsu questo Jobper bloccare fino a quando questo launchperfeziona filo

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
}

🦆 asincrono

Concettualmente, l'asincrono è proprio come il lancio. Inizia una routine separata che è un filo leggero che funziona in concomitanza con tutte le altre coroutine. La differenza è che il lancio restituisce un lavoro e non porta alcun valore risultante, mentre asincrono restituisce un differito, un futuro leggero e non bloccante che rappresenta una promessa di fornire un risultato in seguito.

Quindi asyncavvia un thread in background, fa qualcosa e restituisce immediatamente un token come 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")
}

Puoi usare .await () su un valore differito per ottenere il suo risultato finale, ma anche Deferred è un lavoro, quindi puoi annullarlo se necessario.

Quindi in Deferredrealtà è un Job. Vedi https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/-deferred/index.html

interface Deferred<out T> : Job (source)

🦋 asincrono è desideroso di default

Esiste un'opzione di pigrizia per l'asincronizzazione che utilizza un parametro di avvio opzionale con un valore di CoroutineStart.LAZY. Inizia la routine solo quando il suo risultato è necessario per alcuni aspetti o se viene invocata una funzione di avvio.


11

launche asyncvengono utilizzati per avviare nuove coroutine. Ma li eseguono in modo diverso.

Vorrei mostrare un esempio molto semplice che ti aiuterà a capire molto facilmente la differenza

  1. lanciare
    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;
    }
}

In questo esempio, il mio codice sta scaricando 3 dati facendo clic sul btnCountpulsante e mostrando la pgBarbarra di avanzamento fino al completamento di tutto il download. Ci sono 3 suspendfunzioni downloadTask1(), downloadTask2()e downloadTask3()che scarica i dati. Per simularlo, ho usato delay()in queste funzioni. Queste funzioni attende 5 seconds, 8 secondse 5 secondsrispettivamente.

Come abbiamo usato launchper avviare queste funzioni di sospensione, launchle eseguiremo in sequenza (una per una) . Ciò significa che, downloadTask2()inizierebbe dopo downloadTask1()il completamento e downloadTask3()inizierà solo dopo downloadTask2()il completamento.

Come nello screenshot dell'output Toast, il tempo totale di esecuzione per completare tutti e 3 i download porterebbe a 5 secondi + 8 secondi + 5 secondi = 18 secondi conlaunch

Esempio di avvio

  1. async

Come abbiamo visto, ciò launchrende l'esecuzione sequentiallyper tutte e 3 le attività. Era il momento di completare tutte le attività 18 seconds.

Se tali attività sono indipendenti e se non necessitano del risultato di calcolo di altre attività, possiamo farle funzionare concurrently. Avrebbero iniziato contemporaneamente ed eseguito contemporaneamente in background. Questo può essere fatto con async.

asyncrestituisce un'istanza di Deffered<T>tipo, dove Tè il tipo di dati restituiti dalla nostra funzione di sospensione. Per esempio,

  • downloadTask1()ritornerebbe Deferred<String>come String è il tipo di funzione di ritorno
  • downloadTask2()ritornerebbe Deferred<Int>come Int è il tipo di funzione di ritorno
  • downloadTask3()ritornerebbe Deferred<Float>come Float è il tipo di funzione di ritorno

Possiamo usare l'oggetto return asyncdi tipo Deferred<T>per ottenere il valore restituito nel Ttipo. Questo può essere fatto con la await()chiamata. Controlla sotto il codice per esempio

        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
        }

In questo modo, abbiamo avviato tutte e 3 le attività contemporaneamente. Quindi, il mio tempo totale di esecuzione per il completamento sarebbe solo quello 8 secondsche è il tempo downloadTask2()perché è il più grande di tutte e 3 le attività. Puoi vederlo nel seguente screenshot inToast message

attendo esempio


1
Grazie per launchasync
averlo

Hai usato il lancio una volta per tutte le attività e l'asincrono per ognuna. Forse è più veloce perché ognuno è stato lanciato in un altro coroutine e non aspetta qualcuno? Questo è un confronto errato. Di solito le prestazioni sono le stesse. Una differenza fondamentale è che il lancio avvia sempre una nuova routine anziché un asincrono che divide quella del proprietario. Un altro fattore è che se una delle attività asincrone fallisce per un motivo, la coroutine padre fallirà. Ecco perché async non è così popolare come il lancio.
p2lem8dev,

1
Questa risposta non è corretta, confrontando l'asincronizzazione con le funzioni di sospensione direttamente invece del lancio. Invece di chiamare la funzione di sospensione direttamente nell'esempio, se chiami launch (Dispatchers.IO) {downloadTask1 ()} vedrai che entrambi vengono eseguiti contemporaneamente, non in sequenza , non sarai in grado di ottenere output ma vedrai che è non sequenziale. Inoltre, se non concatenate deferred.await () e chiamate deferred.await () separatamente, vedrete che l'asincronizzazione è sequenziale.
Tracia il

2
-1 questo è semplicemente sbagliato. Entrambi launche asyncinizieranno nuove coroutine. Stai confrontando una singola coroutine senza figli con una singola coroutine con 3 figli. È possibile sostituire ciascuna delle asyncinvocazioni con launche assolutamente nulla cambierebbe per quanto riguarda la concorrenza.
Moira,

Il rumore estraneo in questa risposta sta aggiungendo complessità che è al di fuori dell'argomento della routine comune.
truthadjustr,

6
  1. entrambi i costruttori di coroutine, ovvero launch e async, sono sostanzialmente lambda con ricevitore di tipo CoroutineScope, il che significa che il loro blocco interno viene compilato come una funzione di sospensione, quindi entrambi funzionano in modalità asincrona ED entrambi eseguiranno il blocco in sequenza.

  2. La differenza tra lancio e asincrono è che consentono due diverse possibilità. Il generatore di avvio restituisce un processo, tuttavia la funzione asincrona restituirà un oggetto differito. Puoi usare launch per eseguire un blocco che non ti aspetti alcun valore restituito da esso, ad esempio scrivere su un database o salvare un file o elaborare qualcosa che in pratica ha appena chiamato per il suo effetto collaterale. D'altra parte, l'asincronizzazione che restituisce un differito come precedentemente indicato restituisce un valore utile dall'esecuzione del suo blocco, un oggetto che avvolge i tuoi dati, quindi puoi usarlo principalmente per il suo risultato ma probabilmente anche per il suo effetto collaterale. NB: puoi rimuovere il differito e ottenere il suo valore usando la funzione wait, che bloccherà l'esecuzione delle tue dichiarazioni fino a quando non verrà restituito un valore o se verrà generata un'eccezione!

  3. entrambi i costruttori di coroutine (lancio e asincrono) sono cancellabili.

  4. nient'altro ?: sì con il lancio se viene generata un'eccezione all'interno del suo blocco, la coroutine viene automaticamente cancellata e le eccezioni vengono consegnate. D'altra parte, se ciò accade con asincrono, l'eccezione non viene ulteriormente propagata e deve essere catturata / gestita all'interno dell'oggetto rinviato restituito.

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


1
Grazie per questo commento Ha raccolto tutti i punti del thread. Aggiungo che non tutti i lanci vengono annullati, ad esempio Atomic non può essere mai annullato.
p2lem8dev,

4

launch restituisce un lavoro

async restituisce un risultato (lavoro posticipato)

launch with join viene utilizzato per attendere fino a quando il lavoro non termina. semplicemente sospende la coroutine chiamando join (), lasciando il thread corrente libero di fare altri lavori (come eseguire un'altra coroutine) nel frattempo.

async viene utilizzato per calcolare alcuni risultati. Crea un coroutine e restituisce il suo risultato futuro come implementazione di Deferred. La corsa corrente viene annullata quando viene annullato il differito risultante.

Si consideri un metodo asincrono che restituisce un valore stringa. Se il metodo asincrono viene utilizzato senza attesa, verrà restituita una stringa rinviata, ma se viene utilizzato waitit otterrai una stringa come risultato

La differenza chiave tra asincrono e avvio. Differito restituisce un valore particolare di tipo T al termine dell'esecuzione di Coroutine, mentre Job non lo fa.


0

Async vs Launch Async vs Launch Diff Image

launch / async nessun risultato

  • Utilizzare quando non è necessario il risultato,
  • Non bloccare il codice dove viene chiamato,
  • Corri in parallelo

asincrono per il risultato

  • Quando devi attendere il risultato e puoi correre in parallelo per efficienza
  • Blocca il codice dove viene chiamato
  • correre in parallelo
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.