Qual é a diferença entre launch / join e async / waiting nas corotinas do Kotlin

156

Na kotlinx.coroutinesbiblioteca, você pode iniciar uma nova rotina usando launch(com join) ou async(com await). Qual a diferença entre eles?

Roman Elizarov
fonte

Respostas:

232
  • launché usado para disparar e esquecer a rotina . É como iniciar um novo tópico. Se o código dentro do launchfinalizar com exceção, ele será tratado como exceção não capturada em um encadeamento - geralmente impresso para stderr nos aplicativos JVM de back-end e trava os aplicativos Android. joiné usado para aguardar a conclusão da corotina lançada e não propaga sua exceção. No entanto, uma rotina infantil rotineira travada também cancela seu pai com a exceção correspondente.

  • asyncé usado para iniciar uma rotina que calcula algum resultado . O resultado é representado por uma instância de Deferrede você deve usá await-lo. Uma exceção não capturada dentro do asynccódigo é armazenada dentro do resultado Deferrede não é entregue em nenhum outro lugar; ela será eliminada silenciosamente, a menos que seja processada. Você NÃO DEVE esquecer a corotina que você iniciou com a assíncrona .

Roman Elizarov
fonte
1
O Async é o construtor de corotina certo para chamadas de rede no Android?
Faraaz 30/11
O construtor certo da corotina depende do que você está tentando realizar #
7787 Roman Elizarov
9
Você pode elaborar sobre "Você NÃO DEVE esquecer a corotina que você iniciou com a assíncrona"? Existem truques que não se esperaria, por exemplo?
Luis
2
"Uma exceção não detectada dentro do código assíncrono é armazenada dentro do Adiado resultante e não é entregue em nenhum outro lugar; ela será eliminada silenciosamente, a menos que seja processada."
Roman Elizarov
9
Se você esquecer o resultado do assíncrono, ele terminará e será coletado como lixo. No entanto, se ele travar devido a algum bug no seu código, você nunca aprenderá sobre isso. É por isso que.
Roman Elizarov
77

Acho que este guia https://github.com/Kotlin/kotlinx.coroutines/blob/master/coroutines-guide.md é útil. Vou citar as partes essenciais

🦄 corotina

Essencialmente, as corotinas são fios leves.

Assim, você pode pensar na corotina como algo que gerencia o encadeamento de maneira muito eficiente.

🐤 lançamento

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
}

Então launchinicia um thread em segundo plano, faz alguma coisa e retorna um token imediatamente como Job. Você pode chamar joinisso Jobpara bloquear até que este launchsegmento seja concluído

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
}

🦆 assíncrono

Conceitualmente, assíncrono é como o lançamento. Inicia uma corotina separada, que é um encadeamento leve que funciona simultaneamente com todas as outras corotinas. A diferença é que o lançamento retorna um trabalho e não carrega nenhum valor resultante, enquanto o async retorna um adiado - um futuro leve e sem bloqueio que representa uma promessa de fornecer um resultado posteriormente.

Então asyncinicia um thread em segundo plano, faz alguma coisa e retorna um token imediatamente como 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")
}

Você pode usar .await () em um valor adiado para obter seu resultado final, mas adiado também é um trabalho, para que você possa cancelá-lo, se necessário.

Então Deferredé realmente um Job. Consulte https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/-deferred/index.html

interface Deferred<out T> : Job (source)

🦋 assíncrono está ansioso por padrão

Há uma opção de preguiça para sincronizar usando um parâmetro de início opcional com o valor CoroutineStart.LAZY. Ele inicia a rotina somente quando seu resultado é necessário por alguns aguardam ou se uma função de início é chamada.

onmyway133
fonte
12

launche asyncsão usados ​​para iniciar novas corotinas. Mas, eles os executam de maneira diferente.

Eu gostaria de mostrar um exemplo muito básico que o ajudará a entender a diferença com muita facilidade

  1. lançamento
    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;
    }
}

Neste exemplo, meu código está baixando 3 dados ao clicar no btnCountbotão e mostrando a pgBarbarra de progresso até que todo o download seja concluído. Existem 3 suspendfunções downloadTask1(), downloadTask2()e downloadTask3()que faz download de dados. Para simular, usei delay()essas funções. Essas funções aguardam e 5 seconds, respectivamente.8 seconds5 seconds

Como usamos launchpara iniciar essas funções de suspensão, as launchexecutamos sequencialmente (uma a uma) . Isso significa que, downloadTask2()iniciaria após a downloadTask1()conclusão e downloadTask3()só seria iniciado após a downloadTask2()conclusão.

Como na captura de tela de saída Toast, o tempo total de execução para concluir todos os 3 downloads levaria a 5 segundos + 8 segundos + 5 segundos = 18 segundos comlaunch

Exemplo de Lançamento

  1. assíncrono

Como vimos, isso launchfaz a execução sequentiallyde todas as 3 tarefas. O tempo para concluir todas as tarefas era 18 seconds.

Se essas tarefas forem independentes e se não precisarem do resultado da computação de outras tarefas, podemos executá-las concurrently. Eles começariam ao mesmo tempo e executariam simultaneamente em segundo plano. Isso pode ser feito com async.

asyncretorna uma instância do Deffered<T>tipo, onde Tsão os tipos de dados que nossa função de suspensão retorna. Por exemplo,

  • downloadTask1()retornaria Deferred<String>como String é o tipo de função de retorno
  • downloadTask2()retornaria Deferred<Int>como Int é o tipo de função de retorno
  • downloadTask3()retornaria Deferred<Float>como Float é o tipo de função de retorno

Podemos usar o objeto de retorno de asyncdo tipo Deferred<T>para obter o valor retornado no Ttipo. Isso pode ser feito com a await()chamada. Verifique o código abaixo, por exemplo

        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
        }

Dessa forma, lançamos as três tarefas simultaneamente. Portanto, meu tempo total de execução seria apenas o 8 secondstempo necessário downloadTask2(), pois é o maior de todas as 3 tarefas. Você pode ver isso na seguinte captura de tela emToast message

aguarde exemplo

Kushal
fonte
1
Obrigado por mencionar que launché para async
diversão
Você usou o lançamento uma vez para todas as tarefas e o assíncrono para cada uma. Talvez seja mais rápido, porque cada um foi lançado em outra rotina e não espera alguém? Esta é uma comparação incorreta. Normalmente, o desempenho é o mesmo. Uma diferença importante é que o lançamento sempre inicia uma nova rotina em vez de assíncrona que divide a do proprietário. Um fator a mais é que, se uma das tarefas assíncronas falhar por um motivo, a rotina pai também falhará. É por isso que o assíncrono não é tão popular quanto o lançamento.
p2lem8dev 6/03
1
Esta resposta não está correta, comparando funções assíncronas com suspensas diretamente em vez de iniciar. Em vez de chamar a função de suspensão diretamente no exemplo, se você chamar launch (Dispatchers.IO) {downloadTask1 ()}, verá que ambos são executados simultaneamente, não sequencialmente , não será possível obter resultados, mas verá que é não sequencial. Além disso, se você não concatenar deferred.await () e chamar deferred.await () separadamente, verá que o assíncrono é seqüencial.
Trácia
2
-1 isso está completamente errado. Ambos launche asynciniciarão novas corotinas. Você está comparando uma única corotina sem filhos com uma única corotina com três filhos. Você pode substituir cada uma das asyncinvocações launche absolutamente nada mudaria em relação à simultaneidade.
Moira
O ruído estranho nesta resposta está adicionando complexidade que está fora do tópico de co-rotina.
truthadjustr
6
  1. os dois construtores de corotina, nomeadamente launch e async, são basicamente lambdas com um receptor do tipo CoroutineScope, o que significa que seu bloco interno é compilado como uma função de suspensão, portanto, ambos são executados em modo assíncrono E ambos executam seu bloco sequencialmente.

  2. A diferença entre o lançamento e o assíncrono é que eles permitem duas possibilidades diferentes. O construtor de lançamento retorna um trabalho, no entanto, a função assíncrona retornará um objeto adiado. Você pode usar o launch para executar um bloco que não espera nenhum valor retornado, por exemplo, gravar em um banco de dados ou salvar um arquivo ou processar algo basicamente chamado apenas de seu efeito colateral. Por outro lado, a assíncrona que retorna um adiado como afirmei anteriormente retorna um valor útil da execução de seu bloco, um objeto que agrupa seus dados, para que você possa usá-lo principalmente como resultado, mas possivelmente também como efeito colateral. Nota: você pode retirar o diferido e obter seu valor usando a função aguardar, que bloqueará a execução de suas instruções até que um valor seja retornado ou exceções sejam lançadas!

  3. o construtor de corotina (inicialização e async) é cancelável.

  4. mais alguma coisa ?: sim com o lançamento, se uma exceção for lançada dentro de seu bloco, a corotina é automaticamente cancelada e as exceções são entregues. Por outro lado, se isso acontecer com a assíncrona, a exceção não será propagada mais e deverá ser capturada / manipulada dentro do objeto Adiado retornado.

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

AouledIssa
fonte
1
Obrigado por este comentário. Ele coletou todos os pontos do segmento. Eu acrescentaria que nem todos os lançamentos são cancelados, por exemplo, o Atomic nunca pode ser cancelado.
p2lem8dev 6/03
4

lançamento retorna um trabalho

async retorna um resultado (trabalho adiado)

O lançamento com junção é usado para aguardar a conclusão do trabalho. ele simplesmente suspende a corotina chamando join (), deixando o encadeamento atual livre para realizar outro trabalho (como executar outra corotina) enquanto isso.

assíncrono é usado para calcular alguns resultados. Ele cria uma rotina e retorna seu resultado futuro como uma implementação do Adiado. A corrotina em execução é cancelada quando o diferido resultante é cancelado.

Considere um método assíncrono que retorna um valor de sequência. Se o método assíncrono for usado sem aguardar, ele retornará uma sequência adiada, mas se for aguardado, você obterá uma sequência como resultado

A principal diferença entre assíncrono e inicialização. Adiado retorna um valor específico do tipo T depois que a Coroutine termina a execução, enquanto Job não.

Margem
fonte
0

Async vs Launch Async vs Launch Diff Image

iniciar / assíncrono sem resultado

  • Use quando não precisar de resultado,
  • Não bloqueie o código onde é chamado,
  • Executar em paralelo

assíncrono para resultado

  • Quando você precisa esperar o resultado e pode executar em paralelo para obter eficiência
  • Bloqueie o código onde é chamado
  • correr em paralelo
Vinod Kamble
fonte