¿Cuál es la diferencia entre lanzamiento / unión y asíncrono / espera en las rutinas de Kotlin?


Respuestas:


232
  • launchSe utiliza para disparar y olvidar la corutina . Es como comenzar un nuevo hilo. Si el código dentro del launchtermina con excepción, entonces se trata como una excepción no capturada en un hilo, generalmente impreso en stderr en aplicaciones JVM de back-end y bloquea las aplicaciones de Android. joinse utiliza para esperar la finalización de la rutina lanzada y no propaga su excepción. Sin embargo, una rutina infantil caída también cancela a su padre con la excepción correspondiente.

  • asyncse usa para iniciar una rutina que calcula algún resultado . El resultado está representado por una instancia de Deferredy debe usarlo await. Una excepción no detectada dentro del asynccódigo se almacena dentro del resultado Deferredy no se entrega en ningún otro lugar, se eliminará silenciosamente a menos que se procese. NO DEBE olvidarse de la rutina que comenzó con async .


1
¿Async es el creador de rutinas adecuado para llamadas de red en Android?
Faraaz

El generador de corutina correcto depende de lo que está tratando de lograr
Roman Elizarov

9
¿Puede dar más detalles sobre "NO DEBE olvidarse de la rutina que comenzó con async"? ¿Hay trucos que uno no esperaría, por ejemplo?
Luis

2
"Una excepción no detectada dentro del código asíncrono se almacena dentro del Diferido resultante y no se entrega en ningún otro lugar, se eliminará silenciosamente a menos que se procese".
Roman Elizarov

9
Si olvida el resultado de asíncrono, terminará y se recolectará basura. Sin embargo, si falla debido a algún error en su código, nunca aprenderá sobre eso. Es por eso que.
Roman Elizarov

77

Encuentro útil esta guía https://github.com/Kotlin/kotlinx.coroutines/blob/master/coroutines-guide.md . Citaré las partes esenciales

🦄 corutina

Esencialmente, las corutinas son hilos livianos.

Por lo tanto, puede pensar en la rutina como algo que gestiona el hilo de una manera muy eficiente.

🐤 lanzamiento

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
}

Entonces launchcomienza un hilo de fondo, hace algo y devuelve un token inmediatamente como Job. Puede invocar joinesto Jobpara bloquear hasta que este launchhilo se complete

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
}

🦆 asíncrono

Conceptualmente, async es como el lanzamiento. Comienza una rutina separada que es un hilo liviano que funciona simultáneamente con todas las otras rutinas. La diferencia es que el lanzamiento devuelve un Trabajo y no tiene ningún valor resultante, mientras que async devuelve un Diferido, un futuro ligero y sin bloqueo que representa una promesa de proporcionar un resultado más adelante.

Entonces asynccomienza un hilo de fondo, hace algo y devuelve un token inmediatamente 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")
}

Puede usar .await () en un valor diferido para obtener su resultado final, pero diferido también es un trabajo, por lo que puede cancelarlo si es necesario.

Entonces Deferredes en realidad un Job. Ver https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines.experimental/-deferred/index.html

interface Deferred<out T> : Job (source)

🦋 async está ansioso por defecto

Hay una opción de pereza para sincronizar utilizando un parámetro de inicio opcional con un valor de CoroutineStart.LAZY. Inicia la rutina solo cuando alguien espera su resultado o si se invoca una función de inicio.


12

launchy asyncse utilizan para comenzar nuevas corutinas. Pero, los ejecutan de manera diferente.

Me gustaría mostrar un ejemplo muy básico que te ayudará a entender la diferencia muy fácilmente.

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

En este ejemplo, mi código está descargando 3 datos al hacer clic en el btnCountbotón y mostrando pgBarla barra de progreso hasta que se complete toda la descarga. Hay 3 suspendfunciones downloadTask1(), downloadTask2()y downloadTask3()que descarga datos. Para simularlo, lo he usado delay()en estas funciones. Estas funciones espera a 5 seconds, 8 secondsy 5 secondsrespectivamente.

Como hemos usado launchpara iniciar estas funciones de suspensión, las launchejecutaremos secuencialmente (una por una) . Esto significa que downloadTask2()comenzaría después de downloadTask1()completarse y downloadTask3()comenzaría solo después de downloadTask2()completarse.

Como en la captura de pantalla de salida Toast, el tiempo total de ejecución para completar las 3 descargas llevaría a 5 segundos + 8 segundos + 5 segundos = 18 segundos conlaunch

Ejemplo de lanzamiento

  1. asíncrono

Como vimos, eso launchhace que la ejecución sequentiallyde las 3 tareas. El momento para completar todas las tareas fue 18 seconds.

Si esas tareas son independientes y no necesitan el resultado de cálculo de otra tarea, podemos hacer que se ejecuten concurrently. Comenzarían al mismo tiempo y se ejecutarían simultáneamente en segundo plano. Esto se puede hacer con async.

asyncdevuelve una instancia de Deffered<T>tipo, donde Tes el tipo de datos que devuelve nuestra función de suspensión. Por ejemplo,

  • downloadTask1()volvería Deferred<String>como String es el tipo de función de retorno
  • downloadTask2()volvería Deferred<Int>como Int es el tipo de función de retorno
  • downloadTask3()regresaría Deferred<Float>como Float es el tipo de función de retorno

Podemos usar el objeto de devolución from asyncof type Deferred<T>para obtener el valor devuelto en Ttype. Eso se puede hacer con await()llamada. Verifique el siguiente código, por ejemplo

        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
        }

De esta manera, hemos lanzado las 3 tareas al mismo tiempo. Entonces, mi tiempo total de ejecución para completar sería solo 8 secondsel tiempo necesario, downloadTask2()ya que es la mayor de las 3 tareas. Puedes ver esto en la siguiente captura de pantalla enToast message

aguarde ejemplo


1
Gracias por mencionar que launches para diversiones secuenciales , mientras que asyncpara concurrentes
Akbolat SSS

Has utilizado el lanzamiento una vez para todas las tareas y el asíncrono para cada una. ¿Tal vez es más rápido porque cada uno se lanzó en otra rutina y no espera a alguien? Esta es una comparación incorrecta. Por lo general, el rendimiento es el mismo. Una diferencia clave es que el lanzamiento siempre comienza una nueva rutina en lugar de una asíncrona que divide la del propietario. Un factor más es que si una de las tareas asincrónicas fallara por alguna razón, la rutina principal fallará también. Es por eso que async no es tan popular como el lanzamiento.
p2lem8dev

1
Esta respuesta no es correcta, comparando funciones asíncronas con funciones suspendidas directamente en lugar de iniciarlas. En lugar de llamar a la función de suspensión directamente, por ejemplo, si llama a launch (Dispatchers.IO) {downloadTask1 ()} verá que ambos se ejecutan simultáneamente, no secuencialmente , no podrá obtener salidas pero verá que es No secuencial. Además, si no concatena deferred.await () y llama a deferred.await () por separado, verá que async es secuencial.
Tracio

2
-1 esto es simplemente incorrecto. Ambos launchy asynccomenzarán nuevas corutinas. Usted está comparando una única corutina sin hijos con una única con 3 niños. Puede reemplazar cada una de las asyncinvocaciones launchy absolutamente nada cambiaría con respecto a la concurrencia.
Moira

El ruido extraño en esta respuesta está agregando complejidad que está fuera del tema de la co-rutina.
truthadjustr

6
  1. ambos constructores de coroutine, es decir, launch y async, son básicamente lambdas con un receptor de tipo CoroutineScope, lo que significa que su bloque interno se compila como una función de suspensión, por lo tanto, ambos se ejecutan en modo asíncrono Y ambos ejecutarán su bloque secuencialmente.

  2. La diferencia entre el lanzamiento y el asíncrono es que permiten dos posibilidades diferentes. El generador de inicio devuelve un trabajo, sin embargo, la función asíncrona devolverá un objeto diferido. Puede usar el lanzamiento para ejecutar un bloque del que no espera ningún valor devuelto, es decir, escribir en una base de datos o guardar un archivo o procesar algo que básicamente solo llama su efecto secundario. Por otro lado, async que devuelve un diferido como dije anteriormente devuelve un valor útil de la ejecución de su bloque, un objeto que envuelve sus datos, por lo que puede usarlo principalmente para su resultado, pero posiblemente también para su efecto secundario. NB: puede quitar el diferido y obtener su valor utilizando la función esperar, que bloqueará la ejecución de sus declaraciones hasta que se devuelva un valor o se generen excepciones.

  3. Ambos creadores de rutina (lanzamiento y asíncrono) son cancelables.

  4. ¿algo más ?: sí con el lanzamiento si se lanza una excepción dentro de su bloque, la rutina se cancela automáticamente y se entregan las excepciones. Por otro lado, si eso sucede con asíncrono, la excepción no se propaga más y debe capturarse / manejarse dentro del objeto diferido devuelto.

  5. más sobre las rutinas https://kotlinlang.org/docs/tutorials/coroutines/coroutines-basic-jvm.html https://www.codementor.io/blog/kotlin-coroutines-6n53p8cbn1


1
Gracias por este comentario Recolectó todos los puntos del hilo. Agregaría que no todos los lanzamientos se cancelan, por ejemplo, Atomic no se puede cancelar nunca.
p2lem8dev

4

lanzamiento devuelve un trabajo

async devuelve un resultado (trabajo diferido)

launch with join se usa para esperar hasta que el trabajo termine. Simplemente suspende la rutina que llama a join (), dejando el hilo actual libre para realizar otro trabajo (como ejecutar otra rutina) mientras tanto.

async se usa para calcular algunos resultados. Crea una rutina y devuelve su resultado futuro como una implementación de Diferido. La rutina en ejecución se cancela cuando se cancela el aplazamiento resultante.

Considere un método asíncrono que devuelve un valor de cadena. Si el método asíncrono se usa sin esperar, devolverá una cadena diferida, pero si se usa esperar, obtendrá una cadena como resultado

La diferencia clave entre asíncrono y lanzamiento. Diferido devuelve un valor particular de tipo T después de que su Coroutine termina de ejecutarse, mientras que Job no lo hace.


0

Async vs Launch Async vs Launch Diff Image

lanzamiento / asíncrono sin resultado

  • Úselo cuando no necesite resultado,
  • No bloquee el código donde se llama,
  • Correr en paralelo

asíncrono para el resultado

  • Cuando necesita esperar el resultado y puede ejecutarse en paralelo para mayor eficiencia
  • Bloquea el código donde se llama
  • correr en paralelo
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.