Conociendo las corrutinas de Kotlin

Las corrutinas nos permiten escribir código asíncrono de manera secuencial sin bloquear el subproceso de la interfaz de usuario.
Las corrutinas de Kotlin brindan una nueva forma de manejar la simultaneidad y puede usarla en la plataforma Android para simplificar el código ejecutado de forma asíncrona. Las corrutinas se introdujeron desde la versión 1.3 de Kotlin, pero este concepto ha existido desde los albores del mundo de la programación. El primer lenguaje de programación que usa corrutinas se remonta al lenguaje Simula en 1967. En los últimos años, el concepto de rutinas se ha desarrollado rápidamente y ha sido adoptado por muchos lenguajes de programación convencionales, como Javascript, C#, Python, Ruby y Go. Las rutinas de Kotlin se basan en conceptos establecidos de otros lenguajes.
Google recomienda oficialmente las rutinas de Kotlin como una solución para la programación asíncrona en Android.

configuración

dependencies {
    // lifecycle对于协程的扩展封装
    implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.4.1"
    implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.4.1"
    implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.4.1"
    // 协程核心库
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0"
    // 协程Android支持库
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.6.0"
}

CoroutineScope

CoroutineScope es el alcance de la corrutina, que se utiliza para realizar un seguimiento de las corrutinas. Si iniciamos varias corrutinas pero no hay forma de administrarlas de manera uniforme, nuestro código se hinchará y desordenará, e incluso se producirán pérdidas de memoria o de tareas. Para garantizar que se realice un seguimiento de todas las corrutinas, Kotlin no permite que se inicien corrutinas sin un CoroutineScope.
CoroutineScope realiza un seguimiento de todas las corrutinas y, del mismo modo, puede cancelar todas las corrutinas iniciadas por él. Esto es muy útil en el desarrollo de Android, por ejemplo, puede detener la ejecución de la rutina cuando el usuario abandona la interfaz.
Coroutine es un hilo liviano, lo que no significa que no consuma recursos del sistema. Cuando la operación asincrónica lleva mucho tiempo o cuando se produce un error en la operación asincrónica, Coroutine debe cancelarse para liberar recursos del sistema. En el entorno de Android, Coroutine iniciado por cada interfaz (Actividad, Fragmento, etc.) solo tiene sentido en la interfaz. Si el usuario sale de la interfaz mientras espera que se ejecute Coroutine, es posible que no sea necesario continuar con la ejecución de Coroutine. Además, Coroutine también debe ejecutarse en un contexto apropiado; de lo contrario, se producirán errores, como acceder a View en un subproceso que no sea de interfaz de usuario. Por lo tanto, cuando se diseña Coroutine, se requiere que se ejecute dentro de un ámbito (Alcance), de modo que cuando se cancela el Ámbito, todas las Corrutinas secundarias en él se cancelan automáticamente. Entonces, para usar Coroutine, primero debe crear un CoroutineScope correspondiente.

1. GlobalScope: ámbito de rutina global

public object GlobalScope : CoroutineScope {
    /**
     * Returns [EmptyCoroutineContext].
     */
    override val coroutineContext: CoroutineContext
        get() = EmptyCoroutineContext
}

GlobalScope es una implementación singleton que contiene un CoroutineContext vacío y no contiene ningún trabajo. Este ámbito se usa a menudo como código de muestra.
GlobalScope pertenece al ámbito global, lo que significa que el ciclo de vida de la corrutina iniciada por GlobalScope solo está limitado por el ciclo de vida de toda la aplicación. Mientras toda la aplicación siga ejecutándose y la tarea de la corrutina no haya terminado, la corrutina siempre se puede ejecutar, lo que puede causar pérdidas de memoria y, por lo tanto, no se recomienda.

2. MainScope: alcance de la rutina del subproceso principal

/**
 * Creates the main [CoroutineScope] for UI components.
 *
 * Example of use:
 * ```
 * class MyAndroidActivity {
 *     private val scope = MainScope()
 *
 *     override fun onDestroy() {
 *         super.onDestroy()
 *         scope.cancel()
 *     }
 * }
 * ```
 *
 * The resulting scope has [SupervisorJob] and [Dispatchers.Main] context elements.
 * If you want to append additional elements to the main scope, use [CoroutineScope.plus] operator:
 * `val scope = MainScope() + CoroutineName("MyActivity")`.
 */
@Suppress("FunctionName")
public fun MainScope(): CoroutineScope = ContextScope(SupervisorJob() + Dispatchers.Main)

Se utiliza para devolver un ámbito cuyo CoroutineContext es SupervisorJob() + Dispatchers.Main. El modo de programación predeterminado es Main, lo que significa que el entorno de subprocesos de la corrutina es el subproceso principal.
Este alcance se usa a menudo en Actividad/Fragmento, y cuando se destruye la interfaz, se llama al método CoroutineScope.cancel() en onDestroy() para cancelar la corrutina. Este es un nivel superior que se puede usar en desarrollo para obtener el función de alcance.

3, bloqueo de ejecución

Esta es una función de nivel superior que ejecuta una nueva corrutina y bloquea el subproceso actualmente interrumpible hasta que se completa la ejecución de la corrutina. Esta función está diseñada para unir el código de bloqueo común con las bibliotecas escritas en el estilo de suspensión, para usar en la función principal y probar. Esta función se usa principalmente para pruebas, no para el desarrollo diario. Esta corrutina bloqueará el subproceso actual hasta que se complete la ejecución del cuerpo de la corrutina.

4, LifecycleOwner.lifecycleScope

/**
 * [CoroutineScope] tied to this [LifecycleOwner]'s [Lifecycle].
 *
 * This scope will be cancelled when the [Lifecycle] is destroyed.
 *
 * This scope is bound to
 * [Dispatchers.Main.immediate][kotlinx.coroutines.MainCoroutineDispatcher.immediate].
 */
public val LifecycleOwner.lifecycleScope: LifecycleCoroutineScope
    get() = lifecycle.coroutineScope

SupervisorJob() + Dispatchers.Main.immediate
Este atributo extendido es un alcance coroutine consciente del ciclo de vida proporcionado por la biblioteca Lifecycle Ktx de Android, que se usa para devolver un alcance cuyo CoroutineContext es SupervisorJob() + Dispatchers.Main.immediate. Ciclo de vida de LifecycleOwner, este alcance se cancelará cuando se destruya el ciclo de vida. Este es el alcance recomendado en Actividad/Fragmento, porque vinculará el ciclo de vida con el componente de IU actual, y el alcance de la corrutina se cancelará cuando se destruya la interfaz, lo que no provocará una fuga de la corrutina.

5, ViewModel.viewModelScope

/**
 * [CoroutineScope] tied to this [ViewModel].
 * This scope will be canceled when ViewModel will be cleared, i.e [ViewModel.onCleared] is called
 *
 * This scope is bound to
 * [Dispatchers.Main.immediate][kotlinx.coroutines.MainCoroutineDispatcher.immediate]
 */
public val ViewModel.viewModelScope: CoroutineScope
    get() {
        val scope: CoroutineScope? = this.getTag(JOB_KEY)
        if (scope != null) {
            return scope
        }
        return setTagIfAbsent(
            JOB_KEY,
            CloseableCoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
        )
    }

Este atributo de extensión es un atributo de extensión de ViewModel, que es básicamente lo mismo que LifecycleOwner.lifecycleScope. También devuelve un ámbito donde CoroutineContext es SupervisorJob() + Dispatchers.Main.immediate. Se puede cancelar automáticamente cuando se destruye este ViewModel y no causará fugas de rutinas.

6. Use coroutineScope() y supervisorScope() para crear subámbitos

Ambas funciones son funciones de suspensión y deben ejecutarse dentro de una rutina o dentro de una función de suspensión.
Tanto coroutineScope como supervisorScope devolverán un alcance. La diferencia entre ellos es la propagación de excepciones: la excepción dentro de coroutineScope se propagará hacia arriba, y la excepción no detectada de la corrutina secundaria se pasará hacia arriba a la corrutina principal. Cualquier salida anormal de una corrutina secundaria causará Exit como un todo; la excepción dentro de supervisorScope no se propagará hacia arriba, y una corrutina secundaria sale de manera anormal, lo que no afectará la operación de la corrutina principal y las corrutinas hermanas.
Por lo tanto, los escenarios de diseño y aplicación de supervisorScope se utilizan principalmente cuando las subrutinas son entidades de tareas independientes y equivalentes, como un descargador, y cada subrutina es una tarea de descarga. Cuando una tarea de descarga es anormal, no debería afectar a otras. Descargar tareas.

En resumen, con respecto al alcance, se recomienda más usar LifecycleOwner.lifecycleScope en los componentes de la interfaz de usuario y ViewModel.viewModelScope en ViewModel.


Crea una rutina

Hay dos formas comunes de crear rutinas: CoroutineScope.launch() y CoroutineScope.async().
La diferencia entre los dos radica en el valor de retorno: launch crea una nueva corrutina, pero no devuelve el resultado, y devuelve un Trabajo, que se utiliza para la supervisión y cancelación de la corrutina, y es adecuado para escenarios sin valor de retorno. . async crea una nueva rutina, devuelve una subclase diferida de trabajo y puede obtener el valor de retorno cuando se completa a través de await(). La función de valor de retorno de async debe usarse junto con la función de suspensión await().
Además, la gran diferencia entre los dos es que manejan las excepciones de manera diferente: si usa async como el método de apertura de la corrutina más externa, espera obtener finalmente el resultado (o excepción) llamando await, por lo que por defecto no lo hace. lanzar una excepción. Esto significa que si usa async para iniciar una nueva corrutina más externa sin esperar, descartará silenciosamente la excepción.

CoroutineScope.launch()

Ejemplo sencillo:

//MainScope()获取一个协程作用域用于创建协程
private val mScope = MainScope()

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        // 创建一个默认参数的协程,其默认的调度模式为Main 也就是说该协程的线程环境是Main线程
        mScope.launch {
            // 这里就是协程体
            // 延迟1000毫秒  delay是一个挂起函数
            // 在这1000毫秒内该协程所处的线程不会阻塞
            // 协程将线程的执行权交出去,该线程该干嘛干嘛,到时间后会恢复至此继续向下执行
            Log.d(TAG, "launch开启第一个协程")
            delay(1000)
            Log.d(TAG, "launch开启第一个协程结束")
        }
        // 创建一个指定了调度模式的协程,该协程的运行线程为IO线程
        mScope.launch(Dispatchers.IO) {
            // 此处是IO线程模式
            Log.d(TAG, "launch开启第二个协程")
            delay(1000)
            //将协程所处的线程环境切换至指定的调度模式Main 
            //和launch、async及runBlocking不同,withContext不会创建新的协程,常用于切换代码执行所运行的线程。 
            //它也是一个挂起方法,直到结束返回结果。多个withContext是串行执行的, 所以很适合那种一个任务依赖上一个任务返回结果的情况
            withContext(Dispatchers.Main) {
                // 现在这里就是Main线程了  可以在此进行UI操作了
                Log.d(TAG, "切换至主线程")
            }
        }
    }

override fun onDestroy() {
        super.onDestroy()
        // 取消协程 防止协程泄漏  如果lifecycleScope或者viewModelScope则不需要手动取消
        mScope.cancel()
    }

CoroutineScope.async()

Para demostrar la función de valor de retorno de async, debe usarse junto con la función de suspensión await():

//MainScope()获取一个协程作用域用于创建协程
private val mScope = MainScope()

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        mScope.launch {
            // 开启一个IO模式的线程,并返回一个Deferred,Deferred可以用来获取返回值
            // 代码执行到此处时会新开一个协程 然后去执行协程体,父协程的代码会接着往下走
            val deferred = async(Dispatchers.IO) {
                // 模拟耗时
                delay(2000)
                // 返回一个值
                return@async "获得返回值"
            }
            // 等待async执行完成获取返回值,此处并不会阻塞线程,而是挂起,将线程的执行权交出去
            // 等到async的协程体执行完毕后,会恢复协程继续往下执行
            val data = deferred.await()
            Log.d(TAG, "data:$data")
        }
    }

override fun onDestroy() {
        super.onDestroy()
        // 取消协程 防止协程泄漏  如果lifecycleScope或者viewModelScope则不需要手动取消
        mScope.cancel()
    }

Demostrar las capacidades de concurrencia de async:

//MainScope()获取一个协程作用域用于创建协程
private val mScope = MainScope()

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        mScope.launch {
            // 此处有一个需求  同时请求5个接口  并且将返回值拼接起来
            val job1 = async {
                // 请求1
                delay(5000)
                return@async "1"
            }
            val job2 = async {
                // 请求2
                delay(5000)
                return@async "2"
            }
            val job3 = async {
                // 请求3
                delay(5000)
                return@async "3"
            }
            val job4 = async {
                // 请求4
                delay(5000)
                return@async "4"
            }
            val job5 = async {
                // 请求5
                delay(5000)
                return@async "5"
            }
            // 代码执行到此处时,5个请求已经同时在执行了
            // 等待各job执行完 将结果合并
            Log.d(TAG, "async: ${job1.await()} ${job2.await()} ${job3.await()} ${job4.await()} ${job5.await()}"
            )
        }
    }

override fun onDestroy() {
        super.onDestroy()
        // 取消协程 防止协程泄漏  如果lifecycleScope或者viewModelScope则不需要手动取消
        mScope.cancel()
    }

suspender suspender función

La corrutina de Kotlin proporciona la palabra clave suspend, que se usa para definir una función de suspensión, que es una etiqueta que le dice al compilador que esta función debe ejecutarse en la corrutina, y el compilador convertirá la función de suspensión en una máquina de estado finito. versión optimizada de la devolución de llamada.
Cuando la función de suspensión suspende la rutina, no bloquea el hilo.
Una función de suspensión solo se puede usar en una rutina o una función de suspensión.


CoroutineContext

CoroutineContext es una unidad estructural básica de las rutinas de Kotlin. El uso inteligente del contexto coroutine es crucial para lograr el comportamiento correcto del subproceso, el ciclo de vida, las excepciones y la depuración. Consta de los siguientes elementos:

  • Trabajo: Controlar el ciclo de vida de la rutina;
  • CoroutineDispatcher: distribuye tareas a los hilos apropiados;
  • CoroutineName: el nombre de la rutina, que es útil durante la depuración;
  • CoroutineExceptionHandler: maneja las excepciones no detectadas.
    CoroutineContext tiene dos elementos muy importantes: Trabajo y Dispatcher. El trabajo es la instancia actual de Coroutine y Dispatcher determina el subproceso que ejecuta el Coroutine actual. También puede agregar CoroutineName para la depuración y agregar CoroutineExceptionHandler para detectar excepciones.
    Ejemplo sencillo:
val coroutineContext = Job() + Dispatchers.Default + CoroutineName("协程名字")
Log.d(TAG, "coroutineContext:$coroutineContext")

La salida del registro es:

coroutineContext:[JobImpl{Active}@1f12c95, CoroutineName(协程名字), Dispatchers.Default]

Trabajo

El trabajo se utiliza para procesar corrutinas. Para cada corrutina creada (mediante lanzamiento o asíncrono), devolverá una instancia de trabajo, que es el identificador único de la corrutina y es responsable de administrar el ciclo de vida de la corrutina.
La función CoroutineScope.launch devuelve un objeto Job, que representa una tarea asíncrona. Los trabajos tienen un ciclo de vida y se pueden cancelar. Los trabajos también pueden tener una relación jerárquica. Un trabajo puede contener varios trabajos secundarios. Cuando se cancela el trabajo principal, todos los trabajos secundarios se cancelarán automáticamente; cuando se cancela el trabajo secundario o se produce una excepción, el trabajo principal también se cancelará.
Además de crear el objeto Job a través de CoroutineScope.launch, también puede crear el objeto a través del método de fábrica Job(). De forma predeterminada, la falla de un trabajo secundario provocará la cancelación del trabajo principal. Este comportamiento predeterminado se puede modificar a través de SupervisorJob.
Un trabajo principal con múltiples trabajos secundarios espera a que todos los trabajos secundarios se completen (o cancelen) antes de ejecutarse.

ciclo de vida del trabajo

El ciclo de vida del trabajo incluye Nuevo (recién creado), Activo (activo), Completando (completado), Completado (completado), Cancelando (cancelando), Cancelado (cancelado). Si bien no podemos acceder a estos estados directamente, podemos acceder a las propiedades del trabajo: isActive, isCancelled y isCompleted.

API de uso común para trabajos

isActive: Boolean    //是否存活
isCancelled: Boolean //是否取消
isCompleted: Boolean //是否完成
children: Sequence<Job> // 所有子Job
cancel()             // 取消协程
join()               // 堵塞当前线程直到协程执行完毕
cancelAndJoin()      // 两者结合,取消并等待协程完成
cancelChildren()     // 取消所有子协程,可传入CancellationException作为取消原因
attachChild(child: ChildJob) // 附加一个子协程到当前协程上
invokeOnCompletion(handler: CompletionHandler): DisposableHandle //给 Job 设置一个完成通知,当 Job 执行完成的时候会同步执行这个通知函数

Diferido

Al usar async para crear una rutina, puede obtener un Diferido con un valor devuelto.La interfaz Diferida hereda de la interfaz de trabajo y, además, proporciona un método para obtener el resultado devuelto por Coroutine. Dado que Deferred se hereda de la interfaz del trabajo, el contenido relacionado con el trabajo también se aplica a Deferred. Deferred proporciona tres funciones adicionales para manejar operaciones relacionadas con los resultados de ejecución de Coroutine.

await() //用来等待这个Coroutine执行完毕并返回结果
getCompleted() //用来获取Coroutine执行的结果。如果Coroutine还没有执行完成则会抛出 IllegalStateException ,如果任务被取消了也会抛出对应的异常。所以在执行这个函数之前,可以通过 isCompleted 来判断一下当前任务是否执行完毕了。
getCompletionExceptionOrNull() //获取已完成状态的Coroutine异常信息,如果任务正常执行完成了,则不存在异常信息,返回null。如果还没有处于已完成状态,则调用该函数同样会抛出 IllegalStateException,可以通过 isCompleted 来判断一下当前任务是否执行完毕了。

SupervisorTrabajo

/**
 * Creates a _supervisor_ job object in an active state.
 * Children of a supervisor job can fail independently of each other.
 * 
 * A failure or cancellation of a child does not cause the supervisor job to fail and does not affect its other children,
 * so a supervisor can implement a custom policy for handling failures of its children:
 *
 * * A failure of a child job that was created using [launch][CoroutineScope.launch] can be handled via [CoroutineExceptionHandler] in the context.
 * * A failure of a child job that was created using [async][CoroutineScope.async] can be handled via [Deferred.await] on the resulting deferred value.
 *
 * If [parent] job is specified, then this supervisor job becomes a child job of its parent and is cancelled when its
 * parent fails or is cancelled. All this supervisor's children are cancelled in this case, too. The invocation of
 * [cancel][Job.cancel] with exception (other than [CancellationException]) on this supervisor job also cancels parent.
 *
 * @param parent an optional parent job.
 */
@Suppress("FunctionName")
public fun SupervisorJob(parent: Job? = null) : CompletableJob = SupervisorJobImpl(parent)

Los trabajos tienen una relación padre-hijo. Si el trabajo secundario falla, el trabajo principal fallará automáticamente. Es posible que este comportamiento predeterminado no sea el esperado.
SupervisorJob es un trabajo tan especial que los subtrabajos que contiene no se afectan entre sí, y la falla de un subtrabajo no afecta la ejecución de otros subtrabajos. SupervisorJob(parent:Job?) tiene un parámetro principal. Si se especifica este parámetro, el trabajo devuelto es el trabajo secundario del parámetro principal. Si el trabajo principal falla o se cancela, el trabajo de supervisor también se cancelará. Cuando se cancela el Trabajo de supervisor, también se cancelarán todos los subtrabajos del Trabajo de supervisor.
La implementación de MainScope() usa SupervisorJob y un despachador principal:

/**
 * Creates the main [CoroutineScope] for UI components.
 *
 * Example of use:
 * ```
 * class MyAndroidActivity {
 *     private val scope = MainScope()
 *
 *     override fun onDestroy() {
 *         super.onDestroy()
 *         scope.cancel()
 *     }
 * }
 * ```
 *
 * The resulting scope has [SupervisorJob] and [Dispatchers.Main] context elements.
 * If you want to append additional elements to the main scope, use [CoroutineScope.plus] operator:
 * `val scope = MainScope() + CoroutineName("MyActivity")`.
 */
@Suppress("FunctionName")
public fun MainScope(): CoroutineScope = ContextScope(SupervisorJob() + Dispatchers.Main)

Manejo de excepciones de rutina

El manejo normal de las excepciones es usar try-catch. Sin embargo, las excepciones dentro del alcance de la corrutina no se pueden atrapar usando try-catch. En este momento, es necesario usar CoroutineExceptionHandler para atrapar las excepciones dentro del alcance de la corrutina.
Ejemplo sencillo:

//MainScope()获取一个协程作用域用于创建协程
private val mScope = MainScope()

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        mScope.launch(Dispatchers.Default) {
            delay(500)
            Log.e(TAG, "Child 1")
        }
        // 在Child 2添加了异常处理
        mScope.launch(Dispatchers.Default + CoroutineExceptionHandler { coroutineContext, throwable ->
            Log.e(TAG, "CoroutineExceptionHandler: $throwable")
        }) {
            delay(1000)
            Log.e(TAG, "Child 2")
            throw RuntimeException("--> RuntimeException <--")
        }
        mScope.launch(Dispatchers.Default) {
            delay(1500)
            Log.e(TAG, "Child 3")
        }
    }

override fun onDestroy() {
        super.onDestroy()
        // 取消协程 防止协程泄漏  如果lifecycleScope或者viewModelScope则不需要手动取消
        mScope.cancel()
    }

Resultados de salida de registro:

Child 1
Child 2
CoroutineExceptionHandler: java.lang.RuntimeException: --> RuntimeException <--
Child 3

La excepción lanzada en la segunda corrutina se puede capturar usando CoroutineExceptionHandler.
Si una rutina ordinaria genera una excepción no controlada, propagará la excepción a su rutina principal y luego la rutina principal cancelará todas las rutinas secundarias, se cancelará a sí misma y continuará pasando la excepción hacia arriba.
MainScope() se implementa usando SupervisorJob, por lo que el resultado de la ejecución es que después de que Child 2 lanza una excepción, pero no afecta a otros trabajos, Child 3 se ejecutará normalmente.
El uso de supervisorScope también puede lograr un efecto similar a SupervisorJob: una rutina secundaria sale de forma anormal, lo que no afectará el funcionamiento de la rutina principal y las rutinas hermanas.
Ejemplo sencillo:

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val scope = CoroutineScope(Job() + Dispatchers.Default)
        //如果将supervisorScope换成coroutineScope,结果就不是这样了,Child 3就没有了
        scope.launch(CoroutineExceptionHandler { coroutineContext, throwable ->
            Log.e(TAG, "CoroutineExceptionHandler: $throwable")
        }) {
            supervisorScope {
                launch {
                    delay(500)
                    Log.e(TAG, "Child 1 ")
                }
                launch {
                    delay(1000)
                    Log.e(TAG, "Child 2 ")
                    throw  RuntimeException("--> RuntimeException <--")
                }
                launch {
                    delay(1500)
                    Log.e(TAG, "Child 3 ")
                }
            }
        }
    }

Resultados de salida de registro:

Child 1
Child 2
CoroutineExceptionHandler: java.lang.RuntimeException: --> RuntimeException <--
Child 3

Si reemplaza supervisorScope con coroutineScope, el resultado no es así y el niño 3 desaparece:
el resultado de la salida del registro:

Child 1
Child 2
CoroutineExceptionHandler: java.lang.RuntimeException: --> RuntimeException <--

CoroutineDispatcher

CoroutineDispatcher, el programador, define los subprocesos que ejecuta Coroutine. CoroutineDispatcher puede limitar la ejecución de Coroutine en un subproceso determinado, o puede asignarse a un grupo de subprocesos para su ejecución, o no puede limitar su subproceso de ejecución.
CoroutineDispatcher es una clase abstracta y todos los despachadores deben heredar de esta clase para implementar las funciones correspondientes. Dispatchers es una clase auxiliar en la biblioteca estándar que nos ayuda a encapsular el cambio de subprocesos, que puede entenderse simplemente como un conjunto de subprocesos.
Kotlin coroutine preestablece 4 planificadores:

  • Dispatchers.Default: el programador predeterminado, adecuado para procesar cálculos en segundo plano, es un programador de tareas que hace un uso intensivo de la CPU. Si no se especifica ningún despachador al crear una rutina, generalmente se usa como valor predeterminado de forma predeterminada. Bueno para hacer un trabajo intensivo de CPU fuera del hilo principal, los ejemplos incluyen listas de clasificación y análisis de JSON.
    El despachador predeterminado utiliza un grupo compartido de subprocesos en segundo plano para ejecutar sus tareas. Tenga en cuenta que comparte el grupo de subprocesos con Dispatchers.IO, pero el número máximo de simultaneidad es diferente.
  • Dispatchers.IO: utilizado para realizar operaciones de bloqueo de IO, adecuado para realizar operaciones relacionadas con IO, es un programador de tareas intensivo de IO. Adecuado para realizar E/S de disco o red fuera del subproceso principal, los ejemplos incluyen el uso de componentes Room, la lectura o escritura de datos en archivos y la ejecución de cualquier operación de red.
    Comparta un grupo de subprocesos con Dispatchers.Default para ejecutar las tareas internas. Dependiendo de la cantidad de tareas que se ejecuten al mismo tiempo, se crearán subprocesos adicionales cuando sea necesario y se liberarán los subprocesos innecesarios cuando se completen las tareas.
  • Dispatchers.Main: según la plataforma, se inicializará en el despachador correspondiente al subproceso de interfaz de usuario, como el subproceso principal de Android. Este programador solo debe usarse para interactuar con la interfaz y realizar un trabajo rápido. Los ejemplos incluyen llamadas a funciones de suspensión, ejecución de operaciones del marco de interfaz de Android y actualización de objetos LiveData.
  • Dispatchers.Unconfined: no se especifica ningún subproceso, por lo que el subproceso se inicia de forma predeterminada durante la ejecución. Si la subrutina cambia de subprocesos, el siguiente código también continuará ejecutándose en este subproceso.
    Dado que la corrutina secundaria heredará el contexto de la corrutina principal, por conveniencia de uso, generalmente se establece un Dispatcher en la corrutina principal, y luego todas las corrutinas secundarias usan automáticamente este Dispatcher.

conContexto

A diferencia de launch, async y runBlocking, withContext no crea una nueva rutina y, a menudo, se usa para cambiar el subproceso en el que se ejecuta la rutina. También es un método de suspensión hasta que el final devuelve un resultado. Varios withContexts se ejecutan en serie, por lo que es muy adecuado para la situación en la que una tarea depende del resultado devuelto por la tarea anterior.


Interceptor de continuación

ContinuationInterceptor, un interceptor, se usa para interceptar rutinas para realizar algunas operaciones adicionales, como el programador CoroutineDispatcher, que se implementa con interceptores.
Ejemplo simple: interceptor para imprimir registros

//MainScope()获取一个协程作用域用于创建协程
private val mScope = MainScope()

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        mScope.launch(Dispatchers.IO) {
            launch(MyInterceptor()) {
                Log.d(TAG, "1")
                val deferred = async {
                    Log.d(TAG, "2")
                    delay(100)
                    Log.d(TAG, "3")
                    return@async "返回值"
                }
                Log.d(TAG, "4")
                val result = deferred.await()
                Log.d(TAG, "5:$result")
            }.join()
            Log.d(TAG, "6")
        }
    }

override fun onDestroy() {
        super.onDestroy()
        // 取消协程 防止协程泄漏  如果lifecycleScope或者viewModelScope则不需要手动取消
        mScope.cancel()
    }

class MyInterceptor : ContinuationInterceptor {
        override val key: CoroutineContext.Key<*>
            get() = ContinuationInterceptor

        override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> {
            return MyContinuation(continuation)
        }
    }

class MyContinuation<T>(val continuation: Continuation<T>) : Continuation<T> {
        override val context: CoroutineContext
            get() = continuation.context

        override fun resumeWith(result: Result<T>) {
            Log.d(TAG, "result:$result")
            continuation.resumeWith(result)
        }
    }

Resultados de salida de registro:

result:Success(kotlin.Unit)
1
result:Success(kotlin.Unit)
2
4
result:Success(kotlin.Unit)
3
result:Success(返回值)
5:返回值
6

Ocurrieron cuatro intercepciones, en orden: cuando comenzó la rutina (las dos primeras), cuando se suspendió y cuando se devolvió el resultado.
Ejemplo simple: programador de subprocesos

//MainScope()获取一个协程作用域用于创建协程
private val mScope = MainScope()

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        mScope.launch {
            Log.d(TAG, Thread.currentThread().name)
            withContext(MyInterceptor2()) {
                Log.d(TAG, Thread.currentThread().name)
                delay(100)
                Log.d(TAG, Thread.currentThread().name)
            }
            Log.d(TAG, Thread.currentThread().name)
        }
    }

override fun onDestroy() {
        super.onDestroy()
        // 取消协程 防止协程泄漏  如果lifecycleScope或者viewModelScope则不需要手动取消
        mScope.cancel()
    }

class MyInterceptor : ContinuationInterceptor {
        override val key: CoroutineContext.Key<*>
            get() = ContinuationInterceptor

        override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> {
            return MyContinuation(continuation)
        }
    }

class MyContinuation<T>(val continuation: Continuation<T>) : Continuation<T> {
        override val context: CoroutineContext
            get() = continuation.context

        override fun resumeWith(result: Result<T>) {
            Log.d(TAG, "result:$result")
            continuation.resumeWith(result)
        }
    }

Resultados de salida de registro:

main
result:Success(kotlin.Unit)
我的线程池
result:Success(kotlin.Unit)
我的线程池
main

Artículo de referencia:
Wanzi Longwen - Kotlin Coroutine
Combate avanzado de aplicaciones de Kotlin Coroutine (intercambio de núcleo duro, lo sabrá de un vistazo)

Supongo que te gusta

Origin blog.csdn.net/yuantian_shenhai/article/details/124371396
Recomendado
Clasificación