kotlin ----- corrutina

¿Qué es la corrutina?

Para la corrutina de Kotlin en Android, la corrutina es un conjunto de API de cambio de subprocesos proporcionadas oficialmente por Kotlin. Tiene la misma función que el grupo de subprocesos, pero su ventaja es que escribe código asincrónico de forma sincrónica. Debido a que las corrutinas se ejecutan en jvm, jvm no tiene el concepto de corrutinas.

Si la rutina de Kotlin se está ejecutando en el hilo actual, es equivalente a publicar una tarea en la cola del controlador del hilo actual, lo que también provocará un bloqueo. Recuerde que si la rutina no cambia de hilo, también puede provocar un bloqueo. Lo verificaré más tarde .

Bien, ahora empieza a usar

Si se utiliza corrutina

En Kotlin, las corrutinas no están incluidas en la biblioteca estándar, por lo que aún debes introducirlas cuando las uses:

implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.1.1"

implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.1.1"

La segunda biblioteca es utilizada por el entorno Android.

Dos formas de iniciar una corrutina:

  launch {  }
             
   async {  }

Veamos estos dos métodos.

lanzamiento

public fun CoroutineScope.launch(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> Unit
): Job {
    val newContext = newCoroutineContext(context)
    val coroutine = if (start.isLazy)
        LazyStandaloneCoroutine(newContext, block) else
        StandaloneCoroutine(newContext, active = true)
    coroutine.start(start, coroutine, block)
    return coroutine
}

asíncrono

public fun <T> CoroutineScope.async(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> T
): Deferred<T> {
    val newContext = newCoroutineContext(context)
    val coroutine = if (start.isLazy)
        LazyDeferredCoroutine(newContext, block) else
        DeferredCoroutine<T>(newContext, active = true)
    coroutine.start(start, coroutine, block)
    return coroutine
}

Se descubre que estos dos métodos son funciones de extensión de la clase CoroutineScope (alcance de corrutina), por lo que estas dos funciones deben llamarse utilizando el objeto de alcance de corrutina.

La diferencia es que lanch no devolverá el resultado de ejecución, mientras que async devuelve el objeto diferido a través de await() del objeto diferido.

El método puede obtener los resultados de ejecución de la corrutina.

todavía hay uno

withContext(Dispatchers.Main){ 
    
}

el es en realidad

asíncrono { 

}.await()

abreviatura, por lo que bloqueará la corrutina porque el método await() es una función de suspensión

Explique algunos términos:

CoroutineScope: alcance de rutina

public interface CoroutineScope {
    /**
     * The context of this scope.
     * Context is encapsulated by the scope and used for implementation of coroutine builders that are extensions on the scope.
     * Accessing this property in general code is not recommended for any purposes except accessing the [Job] instance for advanced usages.
     *
     * By convention, should contain an instance of a [job][Job] to enforce structured concurrency.
     */
    public val coroutineContext: CoroutineContext
}

Solo hay un atributo en el alcance de la corrutina, que es el contexto de la corrutina, lo que significa que coroutineScope es una encapsulación de CoroutineContext . Se refiere al alcance de ejecución (o ciclo de vida) de una corrutina, que los usuarios utilizan para heredar y cancelar la corrutina y detener operaciones. puedo controlarme

 Contexto de rutina:

El contexto de una corrutina puede entenderse como la información de configuración de la corrutina en ejecución, la configuración requerida para ejecutar la corrutina, como el nombre de la corrutina y el hilo en el que se ejecuta la corrutina.

Permítanme echar un vistazo a la clase coroutineContext: en realidad es equivalente a un contenedor, que almacena varios atributos, similar al almacenamiento hash.

public interface CoroutineContext {

    //重写了+号运算符
    public operator fun plus(context: CoroutineContext): CoroutineContext =
        if (context === EmptyCoroutineContext) this else // fast path -- avoid lambda creation
            context.fold(this) { acc, element ->
                val removed = acc.minusKey(element.key)
                if (removed === EmptyCoroutineContext) element else {
                    // make sure interceptor is always last in the context (and thus is fast to get when present)
                    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 interface Key<E : Element>

        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
    }
}

Para que pueda usarlo así

Veamos la función de inicio: podemos pasar un coroutineContext

 Podemos usarlo así

launch(Dispatchers.Main+CoroutineName("协程名称")) {  }

 Programador de rutinas 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 en 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 pueden ejecutarse en cualquier hilo.

CoroutineStart: 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.

Trabajo

El método launch () devuelve un objeto de trabajo, que puede detectar el ciclo de vida de la corrutina y cancelar la ejecución de la corrutina.
public interface Job : CoroutineContext.Element {
  
    public companion object Key : CoroutineContext.Key<Job>

    //判断协程是否是活的
    public val isActive: Boolean

    
   //判断协程是否完成
    public val isCompleted: Boolean

   //判断协程是否取消
    public val isCancelled: Boolean



    public fun getCancellationException(): CancellationException

    //开始协程
    public fun start(): Boolean


    //取消协程
    public fun cancel(cause: CancellationException? = null)


   

El diferido en realidad hereda el trabajo

Por lo tanto, también existen métodos para operar la corrutina, que también proporciona un método importante, el método await (), para obtener el valor devuelto por la corrutina. Await es una función de suspensión, por lo que cuando se ejecuta aquí, la corrutina actual se bloqueará. . .

public interface Deferred<out T> : Job {

    public suspend fun await(): T

suspender significa suspender en chino

La función de suspensión bloqueará la corrutina actual, pero no bloqueará el hilo que inició la corrutina.

Cuando se declara una suspensión delante de una función, la función se convierte en una función de suspensión. La función de suspensión solo se puede llamar en la función de suspensión, pero esto solo sirve como recordatorio. Lo que realmente desempeña el papel de suspensión es el código en la función. . Entonces, si declara una función de suspensión, pero no hay ninguna operación de suspensión real en ella, el compilador la atenuará, lo que significa que no es necesario declarar esta función. Entonces, ¿para qué sirve esta declaración? El creador lo utiliza para recordarle a la persona que llama que esta función es una función pendiente y que puede consumir mucho tiempo.

Qué hace la función de suspensión:

Cuando la corrutina alcanza la función suspendida, se detendrá aquí. Se apagará para ejecutar la función suspendida. Después de ejecutar la función suspendida, regresará y se ejecutará nuevamente. De hecho, es un mecanismo de devolución de llamada + máquina de estado.

 Si agrega una función de suspensión real proporcionada por el sistema, estará bien.

La relación entre la rutina principal y la rutina secundaria

1. La información del contexto de la rutina secundaria se copiará en la rutina principal a menos que la modifique usted mismo.

2. Una vez que finaliza la rutina principal, finalizarán todas las rutinas secundarias que contiene.

Demuestre el primer punto:

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
         GlobalScope.launch(Dispatchers.Main+CoroutineName("父协程名字")) {
             Log.e("---","协程上下文-1=${this.coroutineContext}")
             launch {
                 Log.e("---","协程上下文-2=${this.coroutineContext}")
             }
             withContext(Dispatchers.IO+CoroutineName("子协程")){
                 Log.e("---","协程上下文-2=${this.coroutineContext}")
             }
         }
    }

Resultados impresos:

E/---: Contexto de rutina-1=[Nombre de rutina(nombre de rutina principal), StandaloneCoroutine{Active}@d2a3906, Dispatchers.Main]
E/---: Contexto de rutina-3=[Nombre de rutina(rutina secundaria), DispatchedCoroutine{ Active}@8fe4bc7, Dispatchers.IO]
E/---: Coroutine context-2=[CoroutineName (nombre de la corrutina principal), StandaloneCoroutine{Active}@24715f4, Dispatchers.Main]

 Descubrimos que la información de configuración del contexto de rutina de la rutina 2 y la rutina principal son las mismas, pero la rutina 3 es diferente, lo que indica que la rutina secundaria heredará la información de configuración de la rutina principal.

 CoroutineExceptionHandler manejo de excepciones de rutina

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


 

Análisis del problema

1. Si la corrutina en ejecución se ejecuta en el subproceso actual que inicia la corrutina, ¿es equivalente a publicar una tarea en la pila de tareas del controlador del subproceso?

Código de prueba:

    @SuppressLint("MissingInflatedId")
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
         GlobalScope.launch(Dispatchers.Main+CoroutineName("父协程名字")) {
              Log.e("---","111")
         }
        Log.e("---","222")
    }

Imprime el resultado:

                com.ejemplo.miprueba2 E 222
                com.ejemplo.miprueba2 E 111

El resultado de impresión 222 está delante de 111, lo que indica que efectivamente se publicó una tarea.

Cómo crear un alcance de rutina

Sabemos que hay dos formas de iniciar una corrutina. De hecho, también existe un tipo de ejecuciónBloqueo, pero este tipo de corrutina iniciada bloqueará el hilo actual. Solo cuando la corrutina finalice, el hilo que inició la corrutina finalizará. lo que provocará una pérdida de memoria. , no se utiliza en proyectos generales.

lanzamiento()

asíncrono()

Pertenecen a las funciones de extensión de CoroutineScope, por lo que deben tener un objeto CoroutineScope para llamarlas.

Método 1:

GlobalScope es un singleton del alcance de la corrutina, por lo que se puede utilizar directamente
@DelicateCoroutinesApi
public object GlobalScope : CoroutineScope {
    /**
     * Returns [EmptyCoroutineContext].
     */
    override val coroutineContext: CoroutineContext
        get() = EmptyCoroutineContext
}
GlobalScope.launch() { 
}

Método 2:

class MainActivity : AppCompatActivity() {
    lateinit var coroutine:CoroutineScope

    @SuppressLint("MissingInflatedId")
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
       coroutine=  CoroutineScope(Dispatchers.IO)  //创建一个协程作用域
        coroutine.launch { 
            Log.e("---","开始了协程")
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        coroutine.cancel() //协程取消
    }

}

Pero hay un problema aquí: si necesito abrir muchos ámbitos de rutina, ¿tengo que cancelar muchas veces?

Como sigue:

package com.example.mytest2

import android.annotation.SuppressLint
import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.provider.Settings.Global
import android.util.Log
import android.view.View
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.*
import kotlin.coroutines.CoroutineContext
import kotlin.math.log
import kotlin.system.measureTimeMillis
import kotlin.time.measureTimedValue

class MainActivity : AppCompatActivity() {
    lateinit var coroutine:CoroutineScope
    lateinit var coroutine1:CoroutineScope

    @SuppressLint("MissingInflatedId")
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
       coroutine=  CoroutineScope(Dispatchers.IO)
        coroutine.launch {
            Log.e("---","开始了协程")
        }

        coroutine1=  CoroutineScope(Dispatchers.IO)
        coroutine1.launch {
            Log.e("---","开始了协程")
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        coroutine.cancel()
        coroutine1.cancel()
    }

}

Optimización: sabemos que el trabajo gestiona las corrutinas, entonces, ¿cómo gestiona CoroutineScope las corrutinas?

diversión pública CoroutineScope.cancel(causa: CancellationException? = null) { 
    val job = coroutineContext[Job] ?: error("El alcance no se puede cancelar porque no tiene un trabajo: $this") 
    job.cancel(cause) 
}

Se descubrió que en realidad llamó a la cancelación del trabajo, por lo que creamos un trabajo y dejamos que un objeto de trabajo administrara la cancelación de múltiples rutinas.



class MainActivity : AppCompatActivity() {
    val job=Job()
    @SuppressLint("MissingInflatedId")
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
      
        CoroutineScope(Dispatchers.IO+job).launch {
            Log.e("---","开始了协程")
        }

         CoroutineScope(Dispatchers.IO+job).launch {
            Log.e("---","开始了协程")
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        job.cancel()
    }

}

 Presta atención a lo anterior.

CoroutineScope (Dispatchers.IO+job) es un método, no crea un objeto, pero un método devuelve un objeto.
public fun CoroutineScope(context: CoroutineContext): CoroutineScope =
    ContextScope(if (context[Job] != null) context else context + Job())

Método 3

Implemente la interfaz CoroutineScope . Debido a que hay una propiedad no implementada en la interfaz CoroutineScope , el heredero debe inicializar esta propiedad. Podemos implementarla usando una clase delegada.

public interface CoroutineScope {

    public val coroutineContext: CoroutineContext
}
实现CoroutineScope 的接口,协程的上下文让MainScope()方法创建的类实现实现
class MainActivity : AppCompatActivity() ,CoroutineScope by MainScope() {
    @SuppressLint("MissingInflatedId")
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        launch {   //这个协程是运行在主线程的,因为mainScope创建的协程上下文中的线程是主线程
            
        }
    }
    override fun onDestroy() {
        super.onDestroy()
        cancel()
    }
}

Echemos un vistazo al método MainScope(),

public fun MainScope(): CoroutineScope = ContextScope(SupervisorJob() + Dispatchers.Main)
注意这个方法创建的协程上下文的指定线程是主线程,所以说如果子协程不指定线程的话,子协程也是在主线程中运行

Camino 4:

Si administramos la rutina nosotros mismos, será problemático y, a veces, nos olvidamos de cancelarla, lo que fácilmente puede causar pérdidas de memoria, por lo que Android nos proporciona algunas bibliotecas.

//第四种: android提供的和lifecycle相关的库
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.1'
lifecycleScope.launch {  } 用在activity和fragment中

viewModelScope.launch{} 用在viewModel中
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1'
Dirección del sitio web oficial

Análisis de los principios de las corrutinas.

El principio de no bloqueo de las corrutinas en realidad se implementa mediante el mecanismo de devolución de llamada + máquina de estado.

Artículo de referencia:

Avance de sintaxis de Kotlin: rutina (2) Principio de función de suspensión_Volar sobre el blog de la ciudad en ese momento-blog CSDN

suspender: significa suspender
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_coroutine)
        val launch = GlobalScope.launch(Dispatchers.Main) {   //实质上相当于往主线程中post了一个新任务,这个任务就是{}闭包的内容

            println("thread name= ${Thread.currentThread().name}")
            println("======12")
            delay(500)
            println("======23")
        }
        println("--end--")
    }


Echemos un vistazo a la función de inicio:

public fun CoroutineScope.launch(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> Unit   //执行的也是也是我们传入好的闭包,只是给它改成了挂起函数
): Job {
    val newContext = newCoroutineContext(context)
    val coroutine = if (start.isLazy)
        LazyStandaloneCoroutine(newContext, block) else
        StandaloneCoroutine(newContext, active = true)
    coroutine.start(start, coroutine, block)
    return coroutine
}

Analiza este código:

class CoroutineActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_coroutine)

        
        运行到挂起函数的时候,相当于线程执行到这里切断了,一部分去执行挂起函数,一部分去按顺序去执行
        val launch = GlobalScope.launch(Dispatchers.Main) {  //挂起函数
            println("thread name= ${Thread.currentThread().name}")
            println("======12")
            delay(500)
            println("======23")
        }
        println("--end--")  //接下来的内容
    }
}

Cabe señalar que la palabra clave suspender es solo un mensaje y no puede desempeñar un papel de suspensión. Los métodos de suspensión incluyen withcontent (), lanzamiento, etc.

[Comienza la escuela Mashang] ¿La suspensión de las corrutinas de Kotlin es tan mágica y difícil de entender? Hoy le quité el código skin_bilibili_bilibili que comienza la escuela (kaixue.io): número 2 de la rutina de Kotlin. Si tiene alguna idea después de leer esto, ¡deje un mensaje para discutirla! https://www.bilibili.com/video/BV1KJ41137E9?spm_id_from=333.999.0.0

Supongo que te gusta

Origin blog.csdn.net/xueyoubangbang/article/details/123095882
Recomendado
Clasificación