Sintaxis avanzada de Kotlin: conceptos básicos de corrutina (1)

1. Comprensión de las corrutinas de Kotlin

Las corrutinas no son un concepto nuevo, sino un concepto muy antiguo. Muchos lenguajes admiten corrutinas. Se recomienda ir al navegador para comprender la historia y los conceptos básicos de las corrutinas. Aquí solo hablaremos de las corrutinas en kotlin. efecto.

Desde la perspectiva de la implementación del código: la capa inferior de la rutina de Kotlin se implementa mediante subprocesos, que es un marco de subprocesos completamente encapsulado para que lo utilicen los desarrolladores. Una rutina en Kotlin puede entenderse como una tarea de ejecución que se ejecuta en un hilo, y la tarea se puede cambiar entre diferentes hilos. Un hilo puede ejecutar varias rutinas al mismo tiempo. El diagrama conceptual es el siguiente: Un hilo puede ejecutar varias rutinas al mismo tiempo
(Realizar tareas), si es necesario, la rutina en el subproceso 1 se puede cambiar al subproceso 2 para ejecutarse. Reflejado en nuestro desarrollo de Android, nuestras solicitudes de red y otras tareas que requieren mucho tiempo, como operaciones de bases de datos, se ejecutan en el hilo IO. Cuando obtenemos los datos, la operación de actualización de la página se ejecuta en el hilo principal.
Insertar descripción de la imagen aquí

Desde la perspectiva del desarrollador: las corrutinas de Kotlin pueden escribir código de ejecución asincrónica de manera sincrónica y resolver el infierno de anidamiento de las devoluciones de llamadas de cambio de subprocesos . No es necesario bloquear el hilo cuando la rutina está suspendida y está casi libre.
Esto refleja nuestro desarrollo de Android. En el pasado, después de obtener los datos en el hilo IO, queríamos volver al hilo principal para actualizar la interfaz de usuario. Generalmente usábamos devoluciones de llamada de la interfaz Handler o CallBack. El resultado de esto es que cuando Realizar continuamente las operaciones anteriores llevará mucho tiempo y es fácil caer en el infierno de anidación, lo que afecta gravemente la legibilidad y belleza del código.

2. Cómo crear una corrutina

Queremos usar corrutinas en Android Studio. Además de introducir soporte para Kotlin, también necesitamos introducir dos bibliotecas de soporte para corrutinas.

// 协程核心库
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.1"
// 协程Android支持库
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.1"

Cómo crear una corrutina

2.1.) runBlocking: esta es una función de nivel superior que iniciará una nueva rutina y bloqueará el hilo que la llama hasta que se ejecute el código interno. El valor de retorno es una T genérica.
public fun <T> runBlocking(context: CoroutineContext = EmptyCoroutineContext, block: suspend CoroutineScope.() -> T): T
2.2) CoroutineScope.launch: inicia una corrutina a través del método de extensión de lanzamiento de un alcance de corrutina. No bloqueará el hilo que la llama. El valor de retorno es Job.
public fun CoroutineScope.launch(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> Unit
): Job
2.3) CoroutineScope.async: inicie una corrutina a través del método de extensión asíncrono de un alcance de corrutina. No bloqueará el hilo que la llama y el valor de retorno es Diferido.
public fun <T> CoroutineScope.async(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> T
): Deferred<T>

Generalmente usamos dos métodos 2) y 3) para iniciar una corrutina. El método 1) bloqueará el hilo, por lo que básicamente no usaremos la función runBlocking en el desarrollo, pero se puede usar para depurar código.
Permítanme hablar sobre la diferencia entre async y launch de antemano. Hablaré de ello más adelante con ejemplos:
el valor de retorno es diferente: la última línea de expresión de código en el cuerpo de la función async devolverá el resultado como resultado, que es el T genérico en diferido. Podemos usar other La función de rutina obtiene este resultado de ejecución, pero el lanzamiento no tiene tal valor de retorno.

Código de muestra: GlobalScope es un alcance de rutina que ya se proporciona en la biblioteca de rutinas

runBlocking {
    
    
    Log.e("协程","我们使用runBlocking启动了一个协程")
}
GlobalScope.launch {
    
    
    Log.e("协程","我们使用launch启动了一个协程")
}
GlobalScope.async {
    
    
    Log.e("协程","我们使用async启动了一个协程")
}

3. Alcance y contexto de la corrutina

Mencionamos anteriormente que entre las tres formas de crear una corrutina, el lanzamiento y la sincronización requieren que se use el alcance de la corrutina para abrir una corrutina.

3.1 ¿Qué es el alcance de la corrutina?

Alcance de la corrutina CoroutineScope es el alcance de ejecución de la corrutina.
De la siguiente manera: CoroutineScope es una interfaz con un solo atributo de interfaz, CoroutineContext, por lo que CoroutineScope es en realidad una encapsulación del contexto de rutina de CoroutineContext.

public interface CoroutineScope {
    
    
    public val coroutineContext: CoroutineContext
}

3.2 ¿Qué es el contexto de rutina?

CoroutineContext representa el contexto de la corrutina . Antes de comprender el contexto de la corrutina, primero debemos comprender el contexto de contexto. Los desarrolladores de Android deben estar familiarizados con este atributo. Nuestra aplicación, actividad, etc., se definen como un contexto. Entonces, ¿cómo entendemos este contexto? Por ejemplo, cuando tomamos pruebas de comprensión lectora en la escuela, necesitamos entender el significado de un pasaje y lo que debemos hacer. Necesitamos sustituir el artículo original y luego mirar al frente y ver lo que sucede detrás para entender. . Sustituyendo en el código, hemos ejecutado este código. También necesitamos entender cómo ejecutar este código y qué información se necesita para ejecutarlo. Esta información se almacena en el Contexto. Podemos entender el Contexto como un contenedor para el almacenamiento. Configuración información necesaria para la ejecución del código. Entonces CoroutineContext es la información de configuración que necesitamos para iniciar una corrutina.

Echemos un vistazo a la implementación del código fuente de CoroutineContext. Es una interfaz. Hay un elemento de interfaz interno dentro de la interfaz (esta interfaz hereda de CoroutineContext. La interfaz Element tiene una clave, que es similar a un par clave-valor. Podemos usar la clave para juzgar el Elemento). Vemos que
podemos realizar operaciones en CoroutineContext como get (obtener un CoroutineContext basado en la clave), plus (el valor de retorno de una sobrecarga del operador plus es CoroutineContext, lo que significa CoroutineContext + CoroutineContext = CoroutineContext), minusKey (elimine el CoroutineContext correspondiente a la clave) y observe Realmente es como una operación de conjunto. De hecho, CoroutineContext es similar a una operación de conjunto, por lo que podemos adivinar que CoroutineContext tiene muchas subclases. Estas subclases se pueden sumar y unir, y minusKeys se pueden restar para formar una variedad de CoroutineContexts.

public interface CoroutineContext {
    
    

    public operator fun <E : Element> get(key: Key<E>): E?

    public fun <R> fold(initial: R, operation: (R, Element) -> R): R

    public operator fun plus(context: CoroutineContext): CoroutineContext =
        if (context === EmptyCoroutineContext) this else 
            context.fold(this) {
    
     acc, element ->
                val removed = acc.minusKey(element.key)
                if (removed === EmptyCoroutineContext) element else {
    
    
    
                    val interceptor = removed[ContinuationInterceptor]
                    if (interceptor == null) CombinedContext(removed, element) else {
    
    
                        val left = removed.minusKey(ContinuationInterceptor)
                        if (left === EmptyCoroutineContext) CombinedContext(element, interceptor) else
                            CombinedContext(CombinedContext(left, element), interceptor)
                    }
                }
            }

    public fun minusKey(key: Key<*>): CoroutineContext

    public interface Key<E : Element>

    public interface Element : CoroutineContext {
    
    

        public val key: Key<*>

        public override operator fun <E : Element> get(key: Key<E>): E? 
            @Suppress("UNCHECKED_CAST")
            if (this.key == key) this as E else null

        public override fun <R> fold(initial: R, operation: (R, Element) -> R): R =
            operation(initial, this)

        public override fun minusKey(key: Key<*>): CoroutineContext =
            if (this.key == key) EmptyCoroutineContext else this
    }
}
3.3 Alcance de la corrutina y subcorrutinas

Podemos iniciar otra corrutina en una corrutina, y la corrutina abierta de esta manera es una subcorrutina.
Hay dos puntos a tener en cuenta:
1.) El alcance de la corrutina secundaria heredará el contexto de la corrutina en el alcance de la corrutina principal.
2.) Si se cancela la corrutina principal, todas las corrutinas secundarias también se cancelarán. Cancelar

Verificaremos estos dos puntos de atención a continuación.

3.4 Esas subclases de CoroutineContext

CoroutineContext tiene muchas subclases, cada una de las cuales tiene diferentes funciones y juntas constituyen CoroutineContext en el alcance de la corrutina.
Primero imprimamos el coroutineContext de un alcance de corrutina y veamos qué se imprime.

GlobalScope.launch{
    
    
    Log.e("协程的coroutineContext",this.coroutineContext.toString())
}
打印结果:
[StandaloneCoroutine{
    
    Active}@5a7652d, Dispatchers.Default]

Entendemos estas subclases una por una y entendemos su impacto y papel en las corrutinas.

3.4.1) Programador de rutinas de CoroutineDispatcher

Sabemos que las corrutinas se ejecutan en subprocesos y se pueden cambiar a otros subprocesos. CoroutineDispatcher determina en qué subproceso o subprocesos se ejecutan las corrutinas relevantes, por lo que CoroutineDispatcher también se puede llamar el programador de subprocesos de corrutinas.

public abstract class CoroutineDispatcher :
    AbstractCoroutineContextElement(ContinuationInterceptor), ContinuationInterceptor {
    
    

    @ExperimentalStdlibApi
    public companion object Key : AbstractCoroutineContextKey<ContinuationInterceptor, CoroutineDispatcher>(
        ContinuationInterceptor,
        {
    
     it as? CoroutineDispatcher })
}

Kotlin nos proporciona cuatro programadores

public actual object Dispatchers {
    
    
public actual val Default: CoroutineDispatcher = createDefaultDispatcher()
public actual val Main: MainCoroutineDispatcher get() = MainDispatcherLoader.dispatcher
public actual val Unconfined: CoroutineDispatcher = kotlinx.coroutines.Unconfined
public val IO: CoroutineDispatcher = DefaultScheduler.IO
}

Predeterminado: el programador predeterminado, un programador de tareas que consume mucha CPU, generalmente maneja algunas tareas informáticas simples o tareas con un tiempo de ejecución corto. Por ejemplo, cálculo de datos
IO: programador de IO, programador de tareas intensivas de IO, adecuado para realizar operaciones relacionadas con IO. Por ejemplo: solicitudes de red, operaciones de bases de datos, operaciones de archivos, etc.
Principal: programador de UI, solo significativo en la plataforma de programación de UI, utilizado para actualizar la UI, como el hilo principal en Android. Sin límites:
programador sin restricciones, sin programador, actualmente Las corrutinas se pueden ejecutar en cualquier hilo.

De la impresión anterior, podemos saber que el programador de corrutinas de GlobalScope es Dispatchers.Default, entonces, ¿cómo lo cambiamos? Cuando miramos los métodos de lanzamiento y asíncrono anteriormente, vimos que sus primeros parámetros fueron contexto: CoroutineContext. Sí, podemos pasar el contexto que necesitamos desde aquí y sobrescribirá el contexto en el alcance de la rutina.
De la siguiente manera: esperamos iniciar una corrutina para ejecutarla en el subproceso IO.

GlobalScope.launch(Dispatchers.IO){
    
    
    Log.e("协程的coroutineContext",this.coroutineContext.toString())
}
打印结果:
[StandaloneCoroutine{
    
    Active}@db90566, Dispatchers.IO]

Entonces, ¿qué pasa si queremos cambiar el hilo mientras se ejecuta la rutina? Lo más común es que las solicitudes de red estén en el hilo IO y las actualizaciones de la página en el hilo principal. Kotlin nos proporciona una función de nivel superior withContext para cambiar el contexto de la corrutina y ejecutar un fragmento de código.

public suspend fun <T> withContext(
    context: CoroutineContext,
    block: suspend CoroutineScope.() -> T
): T 

Código de muestra:

GlobalScope.launch(Dispatchers.Main) {
    
    
    val result = withContext(Dispatchers.IO) {
    
    
        //网络请求
        "返回结果"
    }
    mBtn.text = result
}
3.4.2)CoroutineName nombre de la corrutina

Nombre de la corrutina: Como sugiere el nombre, es para darle un nombre a la corrutina, no hay nada que decir al respecto, se usa para distinguir mejor la corrutina.

public data class CoroutineName(

    val name: String
) : AbstractCoroutineContextElement(CoroutineName) {
    
    

    public companion object Key : CoroutineContext.Key<CoroutineName>

    override fun toString(): String = "CoroutineName($name)"
}

Código de muestra:

GlobalScope.launch(Dispatchers.Main + CoroutineName("主协程")) {
    
    
    Log.e("协程的coroutineContext",this.coroutineContext.toString())
}
打印结果:
[CoroutineName(主协程), StandaloneCoroutine{
    
    Active}@288ff9, Dispatchers.Main]

El primer punto a tener en cuenta cuando usamos el nombre de la corrutina para verificar la subcorrutina: el alcance de la corrutina de la subcorrutina heredará el contexto de la corrutina en el alcance de la corrutina principal. Comenzamos una subcorrutina en el código anterior
:

GlobalScope.launch(Dispatchers.Main + CoroutineName("主协程")) {
    
    
    Log.e("协程的coroutineContext" , this.coroutineContext.toString())
    launch {
    
    
        Log.e("协程的coroutineContext2" , this.coroutineContext.toString())
    }
}

El resultado de la impresión es el siguiente: Se puede ver que la rutina secundaria en realidad imprimió el mismo nombre de rutina que la rutina principal, lo que indica que el contexto de la rutina principal se ha propagado a la rutina secundaria.

协程的coroutineContext: [CoroutineName(主协程), StandaloneCoroutine{
    
    Active}@288ff9, Dispatchers.Main]
协程的coroutineContext2: [CoroutineName(主协程), StandaloneCoroutine{
    
    Active}@b95b3e, Dispatchers.Main]

Cambiemos un poco el código:

GlobalScope.launch(Dispatchers.Main + CoroutineName("主协程")) {
    
    
    Log.e("协程的coroutineContext" , this.coroutineContext.toString())
    launch(CoroutineName("子协程")) {
    
    
        Log.e("协程的coroutineContext2" , this.coroutineContext.toString())
    }
}

El resultado de la impresión es el siguiente: el contexto de rutina establecido por la rutina secundaria sobrescribe el contexto heredado de la rutina principal.

协程的coroutineContext: [CoroutineName(主协程), StandaloneCoroutine{
    
    Active}@288ff9, Dispatchers.Main]
协程的coroutineContext2: [CoroutineName(子协程), StandaloneCoroutine{
    
    Active}@8aced9f, Dispatchers.Main]
3.4.3) Ciclo de vida de Job y corrutina

Cuando miramos las dos funciones de extensión launch y async anteriormente, podemos ver que el resultado de retorno del lanzamiento es un Trabajo, y el resultado de retorno de async es un Diferido, que en realidad es una subclase de Trabajo.

public interface Job : CoroutineContext.Element 
public interface Deferred<out T> : Job 

Entonces ¿qué es un trabajo?
Una vez iniciada la corrutina, podemos obtener un objeto Job. A través del objeto Job, podemos detectar el estado del ciclo de vida de la corrutina y operar la corrutina (como cancelar la corrutina) . Podemos entender a grandes rasgos a Job como la corrutina misma.
El ciclo de vida de la corrutina: después de que se crea la corrutina, está en el estado Nuevo (nuevo). Después de que se inicia la corrutina (se llama al método start (), está en el estado Activo (activo). Después de La corrutina y todas las subcorrutinas completan sus tareas, se encuentra en el estado Completado. (Completado). Después de que se cancela la corrutina (se llama al método cancelar()), se encuentra en el estado Cancelado. Podemos usar los campos bajo
el trabajo para verificar el estado de la corrutina:
isActive se usa para determinar si la corrutina está activa.
isCancelled se usa
isCompleted se usa para determinar la corrutina ha finalizado
. Además de obtener el estado de la corrutina, hay muchas funciones que se pueden usar. para manipular la corrutina, como:
cancel() para cancelar la corrutina.
start() inicia la corrutina.
await() espera a que se complete la ejecución de la rutina

Verifiquemos el ciclo de vida de la corrutina:

GlobalScope.launch {
    
    
    val job = launch(CoroutineName("子协程")) {
    
    

    }
    Log.e("子协程的状态","${
      
      job.isActive} ${
      
      job.isCancelled} ${
      
      job.isCompleted}")
    delay(1000)
    Log.e("子协程的状态2","${
      
      job.isActive} ${
      
      job.isCancelled} ${
      
      job.isCompleted}")
}
打印结果:
子协程的状态: true false false
子协程的状态2: false false true
GlobalScope.launch {
    
    
    val job = launch(CoroutineName("子协程")) {
    
    
        delay(5000)
    }
    Log.e("子协程的状态","${
      
      job.isActive} ${
      
      job.isCancelled} ${
      
      job.isCompleted}")
    job.cancel()
    Log.e("取消后子协程的状态","${
      
      job.isActive} ${
      
      job.isCancelled} ${
      
      job.isCompleted}")
}
打印结果:
子协程的状态: true false false
取消后子协程的状态: false true false

Usemos el ciclo de vida de la corrutina para verificar la segunda nota de la subcorrutina: si se cancela la corrutina principal, todas las subcorrutinas también se cancelarán.

var childJob : Job? = null
val parentJob = GlobalScope.launch {
    
    
    childJob = launch(CoroutineName("子协程")) {
    
    
        delay(5000)
    }
}
Log.e("父协程的状态" , "${
      
      parentJob.isActive} ${
      
      parentJob.isCancelled} ${
      
      parentJob.isCompleted}")
Handler().postDelayed(Runnable {
    
    
    Log.e("子协程的状态" ,
        "${
      
      childJob?.isActive} ${
      
      childJob?.isCancelled} ${
      
      childJob?.isCompleted}")
    parentJob.cancel()
    Log.e("父协程的状态" ,
        "${
      
      parentJob.isActive} ${
      
      parentJob.isCancelled} ${
      
      parentJob.isCompleted}")
    Log.e("子协程的状态" ,
        "${
      
      childJob?.isActive} ${
      
      childJob?.isCancelled} ${
      
      childJob?.isCompleted}")
} , 1000)
打印结果如下:可以看到父协程取消以后,子协程也取消了。
父协程的状态: true false false
子协程的状态: true false false
父协程的状态: false true false
子协程的状态: false true false
3.4.4) Manejo de excepciones de rutina CoroutineExceptionHandler

Cuando escribimos código, definitivamente nos encontraremos con situaciones anormales. Normalmente usamos try...catch para manejar excepciones, pero es inevitable que ocurran omisiones. CoroutineExceptionHandler es una clase que se especializa en detectar excepciones en corrutinas. Las excepciones que ocurren en corrutinas serán detectadas y devueltas por el método handleException de CoroutineExceptionHandler para su procesamiento.

public interface CoroutineExceptionHandler : CoroutineContext.Element {
    
    
    public companion object Key : CoroutineContext.Key<CoroutineExceptionHandler>

    public fun handleException(context: CoroutineContext, exception: Throwable)
}

handleException devolverá dos parámetros: el primer parámetro es la rutina donde ocurrió la excepción y el segundo parámetro es la excepción que ocurrió.
El ejemplo es el siguiente: lanzamos manualmente una excepción NullPointerException y luego creamos un CoroutineExceptionHandler y lo asignamos a la corrutina.

val exceptionHandler = CoroutineExceptionHandler {
    
     coroutineContext, throwable ->
    Log.e("捕获异常", "${
      
      coroutineContext[CoroutineName]}$throwable")
}

GlobalScope.launch(Dispatchers.Main + CoroutineName("主协程")+exceptionHandler) {
    
    
    Log.e("协程的coroutineContext",this.coroutineContext.toString())
    throw NullPointerException()
}
打印结果:
协程的coroutineContext: [CoroutineName(主协程), com.jf.simple.ThirdActivity$onCreate$$inlined$CoroutineExceptionHandler$1@288ff9, StandaloneCoroutine{
    
    Active}@b95b3e, Dispatchers.Main]

捕获异常: CoroutineName(主协程) :java.lang.NullPointerException
3.4.5) Interceptor de rutina ContinuationInterceptor
public interface ContinuationInterceptor : CoroutineContext.Element {
    
    

    companion object Key : CoroutineContext.Key<ContinuationInterceptor>
}

Como sugiere el nombre, se usa para interceptar corrutinas. Debido a que implica el principio de suspender funciones y se usa relativamente raramente en el desarrollo diario, no lo ampliaremos aquí.

4. Modo de inicio de la rutina

Cuando miramos las funciones de lanzamiento y extensión asíncrona, hay un segundo parámetro, inicio: CoroutineStart. El significado de este parámetro es el modo de inicio de la corrutina.

public enum class CoroutineStart {
    
    
DEFAULT,
LAZY,
ATOMIC,
UNDISPATCHED;
}

Puede ver que CoroutineStart es una clase de enumeración con cuatro tipos.
DEFAULT es el modo de inicio predeterminado. La programación comienza inmediatamente después de que se crea la rutina. Tenga en cuenta que se programa inmediatamente en lugar de ejecutarse inmediatamente. Puede cancelarse antes de la ejecución.
El modo de inicio diferido LAZY no tendrá ningún comportamiento de programación después de la creación y la programación no se producirá hasta que necesitemos que se ejecute. La programación comienza solo cuando necesitamos llamar manualmente a las funciones de inicio, unión o espera del trabajo.
ATOMIC comienza a programar inmediatamente después de que se crea la rutina, pero es diferente del modo DEFAULT. En este modo, después de que se inicia la rutina, debe ejecutarse hasta el primer punto de suspensión antes de responder a la operación de cancelación.
En este modo, la corrutina UNDISPATCHED comenzará a ejecutarse directamente en el hilo actual hasta que alcance el primer punto de suspensión. Es muy similar a ATOMIC, pero UNDISPATCHED se ve muy afectado por el programador.

Código de muestra:
PREDETERMINADO: el código se imprime inmediatamente, lo que indica que la rutina está programada inmediatamente después de su creación.

GlobalScope.launch {
    
    
    Log.e("default启动模式", "协程运行")
}

LAZY: Antes de llamar al método start (), no se imprime, después de llamar al método start (), se imprime el código. La descripción de la rutina no se programará después de la creación y debemos iniciarla manualmente.

val lazyJob = GlobalScope.launch(start = CoroutineStart.LAZY){
    
    
    Log.e("lazy启动模式", "协程运行")
}
//lazyJob.start()

ATÓMICO: después de ejecutar la rutina, no responderá al método cancel() hasta que llegue a la primera función suspendida.

val atomicJob = GlobalScope.launch(start = CoroutineStart.ATOMIC) {
    
    
    Log.e("atomic启动模式" , "运行到挂起函数前")
    delay(100)
    Log.e("atomic启动模式" , "运行到挂起函数后")
}
atomicJob.cancel()
打印结果:
atomic启动模式: 运行到挂起函数前

UNDISPATCHED: Puede ver que los resultados son muy similares a ATOMIC, pero debido a que ATOMIC y UNDISPATCHED se usan relativamente raramente en el desarrollo, no haremos demasiadas distinciones aquí. Si está interesado, puede abrir una subcorrutina y agregar una programador para ejecutar el código y ver la diferencia.

5. Suspender la función

Mencionamos anteriormente: La mayor ventaja de las corrutinas de Kotlin es que escribe código asincrónico de manera sincrónica, lo que se logra mediante la función de suspensión.

La función modificada por la palabra clave suspender se denomina función de suspensión y solo se puede llamar en la corrutina u otra función de suspensión. La característica de la función suspendida es "suspensión y recuperación". Cuando la corrutina encuentra una función suspendida, la corrutina se suspenderá. Una vez ejecutada la función suspendida, la corrutina se reanudará al lugar donde se suspendió y se ejecutará nuevamente.
La suspensión es una suspensión sin bloqueo y no bloquea subprocesos; la recuperación no requiere que la recuperemos manualmente, pero la corrutina lo hace por nosotros.

Aquí demostramos dos ejemplos para comprender el uso de funciones de suspensión. (No hablaré aquí sobre el principio de suspender funciones, comenzaré un capítulo especial más adelante).

5.1) Ejecutar código asincrónico secuencialmente

Código de muestra 1: definimos dos funciones de suspensión, una con un retraso de 1 s y otra con un retraso de 2 s (para simular una solicitud de red), al final ambas devuelven un número entero y queremos sumar los resultados. Sin usar corrutinas, debido a que los retrasos son diferentes, necesitamos usar un método similar a una devolución de llamada para obtener los resultados. Pero usar corrutinas es diferente. Podemos ver en los resultados de la impresión que el código se ejecuta de forma completamente secuencial. El método MeasureTimeMillis puede medir el tiempo de ejecución. Podemos ver que el tiempo de ejecución es un poco más de 3 segundos: Entonces, todo el proceso es como esto. La corrutina se ejecuta para returnNumber1(), se detecta que es una función colgante. La corrutina se cuelga y espera a que se complete returnNumber1(). Se necesitan 1 segundos para que returnNumber1() se complete. La corrutina regresa al lugar donde returnNumber1 () fue llamado, obtiene el resultado y continúa la ejecución, la línea llega a returnNumber2(), se detecta que es una función colgante, la corrutina se cuelga, esperando que se complete returnNumber2(), tarda 2 s en completar returnNumber2( ), la corrutina regresa al lugar donde se llama a returnNumber2() y continúa la ejecución.

suspend fun returnNumber1() : Int {
    
    
    delay(1000L)
    Log.e("returnNumber1" , "调用了returnNumber1()方法")
    return 1
}

suspend fun returnNumber2() : Int {
    
    
    delay(2000L)
    Log.e("returnNumber1" , "调用了returnNumber2()方法")
    return 2
}
GlobalScope.launch {
    
    
    val time = measureTimeMillis {
    
    
        val number1 = returnNumber1()
        Log.e("number1" , "需要获取number1")
        val number2 = returnNumber2()
        Log.e("number2" , "需要获取number2")
        val result = number1 + number2
        Log.e("执行完毕" , result.toString())
    }
    Log.e("运行时间",time.toString())
}
打印结果:
returnNumber1: 调用了returnNumber1()方法
number1: 需要获取number1
returnNumber1: 调用了returnNumber2()方法
number2: 需要获取number2
执行完毕: 3
运行时间: 3010
5.2) async implementa la concurrencia

Tenga en cuenta que la función de suspensión suspende la corrutina actual y no afectará a otras corrutinas.
Modifiquemos el código anterior y coloquemos las dos funciones de suspensión en dos sub-rutinas. El resultado final se obtiene usando await(). La función de await() El método es esperar a que la rutina termine de ejecutarse y obtener el resultado de retorno. Hemos mencionado la diferencia entre lanzamiento y async antes. Uno tiene un valor de retorno del resultado de la ejecución y el otro no, por lo que aquí se usa async.
Código de muestra: Async se usa aquí para abrir dos subcorrutinas. Ambas subcorrutinas tienen funciones de suspensión, por lo que ambas subcorrutinas se suspenderán, pero sus corrutinas principales no se suspenden antes de llamar a la función de suspensión await(). No es así. suspendido, por lo que puede ejecutarse normalmente. Las dos sub-rutinas se ejecutan al mismo tiempo. El tiempo de ejecución final también se puede ver mediante la impresión. La ejecución del código solo toma 2 segundos.

GlobalScope.launch(Dispatchers.Main) {
    
    
    val time = measureTimeMillis {
    
    
        val deferred1 = async {
    
    
            Log.e("--" , "子协程1运行开始")
            returnNumber1()
        }
        Log.e("--" , "开始运行第二个协程")
        val deferred2 = async {
    
    
            Log.e("--" , "子协程2运行开始")
            returnNumber2()
        }
        Log.e("--" , "开始计算结果")

        val result = deferred1.await() + deferred2.await()
        Log.e("执行完毕" , result.toString())

    }
    Log.e("运行时间" , time.toString())
}

打印结果如下:
开始运行第二个协程
开始计算结果
子协程1运行开始
子协程2运行开始
returnNumber1: 调用了returnNumber1()方法
returnNumber1: 调用了returnNumber2()方法
执行完毕: 3
运行时间: 2009

En la siguiente sección explicaremos el principio de suspensión de funciones de la forma más eficaz.

Supongo que te gusta

Origin blog.csdn.net/weixin_43864176/article/details/126234790
Recomendado
Clasificación