Corrutinas de Kotlin - CoroutineContext contexto de corrutina

Corrutinas Kotlin - contexto CoroutineContext

Serie de rutinas de Kotlin:

  • Uso básico de las corrutinas
  • Comprensión contextual de las corrutinas (este artículo)
  • Gestión del alcance de la rutina
  • Uso avanzado común de rutinas

El contexto contextual, todos lo sabemos, en Android puede llamar recursos, iniciar cuatro componentes, obtener servicios del sistema, etc. El contexto de corrutina CoroutineContext puede controlar corrutinas, programar corrutinas, detectar excepciones, etc.

Las rutinas siempre se ejecutan en un contexto de tipo CoroutineContext.

Como aprendimos en el artículo anterior, hay tres formas de iniciar/crear una rutina

launch async runBlocking Observamos su código fuente y podemos ver que el primer parámetro es pasar en un contexto coroutine, por supuesto, el valor predeterminado es EmptyCoroutineContext.

public fun CoroutineScope.launch(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> Unit
): Job {
public fun <T> CoroutineScope.async(
    context: CoroutineContext = EmptyCoroutineContext,
    start: CoroutineStart = CoroutineStart.DEFAULT,
    block: suspend CoroutineScope.() -> T
): Deferred<T> {
public fun <T> runBlocking(context: CoroutineContext
 = EmptyCoroutineContext, block: suspend CoroutineScope.() -> T): T {

Aquí hay algunas clases de implementación de contexto de uso común para ver cómo usarlas

1. Programadores despachadores

Podemos especificar el subproceso en ejecución a través de la corrutina principal, y la corrutina secundaria interna también se puede asignar a un subproceso específico

  CoroutineScope(Dispatchers.Main).launch {

           GlobalScope.launch(Dispatchers.Default) {
                YYLogUtils.w("执行在另一个协程中...")

                delay(1000L)

                YYLogUtils.w("另一个协程执行完毕...")
            }

            val deferred = async(Dispatchers.IO) {
                YYLogUtils.w("切换到另一个协程")
                Thread.sleep(2000)
                return@async "response data"
            }

            val response = deferred.await()
            YYLogUtils.w("response:$response")

            runBlocking(Dispatchers.IO) {
                //异步执行
                YYLogUtils.w("异步执行result1")
                delay(1000L)
                YYLogUtils.w("result1:1234")
            }

            withContext(Dispatchers.IO) {
                //异步执行
                YYLogUtils.w("异步执行result2")
                delay(1000L)
                YYLogUtils.w("result2:123456")
            }
    }

Se puede ver que ya sea el subproceso en ejecución de la corrutina especificada o el subproceso de cambio temporal, volverá a cambiar después de ejecutarse, lo cual está programado por Dispatchers.

La programación de subprocesos comúnmente utilizada es:

  • Dispatchers.Main Hilo principal de Android
  • Dispatchers.Unconfined la estrategia de subprocesamiento del CoroutineScope actual
  • Dispatchers. Valor predeterminado predeterminado, grupo de subprocesos compartidos para JVM
  • Dispatchers.IO Grupo de subprocesos de E/S, el valor predeterminado es 64 subprocesos

Por supuesto, además de los programadores Dispatchers que vienen con el sistema, también podemos implementar nuestro propio programador a través de algunos métodos de extensión para permitir que la corrutina se ejecute en el subproceso que especifiquemos.

Por ejemplo, si queremos ejecutar en subprocesos HandleThread, o subprocesos en el grupo de subprocesos Ejecutores, podemos hacer esto:

    private var mHandlerThread: HandlerThread? = HandlerThread("handle_thread")

    private var mHandler: Handler? = mHandlerThread?.run {
        start()
        Handler(this.looper)
    }

    ...

    GlobalScope.launch(mHandler.asCoroutineDispatcher("handle_thread")) {

        YYLogUtils.w("执行在协程中...")

        delay(1000L)

        YYLogUtils.w("执行完毕...")
    }

Esto se ejecutará en el subproceso HandleThread especificado.

De la misma manera, podemos crear un grupo de subprocesos. Por ejemplo, si creo un grupo de subprocesos único, luego lo convierto en un objeto Dispatcher de despacho de un contexto coroutine, entonces esta corrutina se ejecutará en el subproceso en el grupo de subprocesos que especifiquemos. .

GlobalScope.launch(Executors.newSingleThreadExecutor().asCoroutineDispatcher()) {

    YYLogUtils.w("执行在协程中...")

    delay(1000L)

    YYLogUtils.w("执行完毕...")
}

二、CoroutineName 协程命名

如果一个协程中有多个子协程,如果都张的一样,我想知道谁是谁,我们就可以通过 CoroutineName 来命名,它也是 CoroutineContext 的实现类,使用起来非常的简单,直接构造即可:

GlobalScope.launch(CoroutineName("parent")) {

        async(CoroutineName("child1")) {
            YYLogUtils.w("切换到另一个协程1")
            Thread.sleep(2000)
            return@async "response data"
        }.await()

        async(CoroutineName("child2")) {
            YYLogUtils.w("切换到另一个协程2")
            Thread.sleep(2000)
            return@async "response data"
        }.await()

}

三、Job 协程控制

Job 不是我们协程启动返回的对象吗?用于cancel join 等操作。它虽然是协程启动的返回值,但是同样也是 CoroutineContext 的实现类,一样可以用于启动协程的构造中。

例如我们之前是这么处理的:

var job: Job? = null

...

job = GlobalScope.launch(Dispatchers.Main) {

   YYLogUtils.w("执行在协程中...")

    delay(1000L)

    YYLogUtils.w("执行完毕...")
}


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

我们之前都是这么处理的,例如 launch 的时候返回一个Job,然后通过成员变量在页面退出的时候取消掉这个协程的运行,没毛病。

如果使用构造方法,我们就可以这么操作了:

var job: Job? = null

...

 GlobalScope.launch(job.run { Job() }) {

   YYLogUtils.w("执行在协程中...")

    delay(1000L)

    YYLogUtils.w("执行完毕...")
}


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

或者更简单的,直接就初始化就构造好:

var job: Job = Job()

...

 GlobalScope.launch(job) {

   YYLogUtils.w("执行在协程中...")

    delay(1000L)

    YYLogUtils.w("执行完毕...")
}


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

效果和上面使用返回值的方式一样的,都能在 onDestroy 的时候取消协程。

四、CoroutineExceptionHandler 异常处理

协程的异常处理我们大家都会,不就是try catch嘛,简单

 GlobalScope.launch() {

   YYLogUtils.w("执行在协程中...")

    delay(1000L)

    val num = 999/0

    YYLogUtils.w("执行完毕...")
}

这样都会报错的,如果你没有处理,那么协程可不会自动帮你处理,你的App就会崩溃。我们可以try catch处理即可:

 GlobalScope.launch() {

   YYLogUtils.w("执行在协程中...")

    delay(1000L)

      try {

         val num = 999/0

        } catch (e: Exception) {
            e.printStackTrace()
        }
 
     YYLogUtils.w("执行完毕...")
}

那其实我们传入一个处理异常的上下文对象,一样能实现这个效果,就无需在协程中到处try catch了。

private val exceptionHandler = CoroutineExceptionHandler { coroutineContext, throwable ->
    YYLogUtils.e(throwable.message ?: "Unkown Error")
}

 GlobalScope.launch(exceptionHandler) {

   YYLogUtils.w("执行在协程中...")

    delay(1000L)

    val num = 999/0

    YYLogUtils.w("执行完毕...")
}

这样我们就可以在外部统一的处理异常信息了。

基于这样的处理,我们还能做一个简单的扩展方法,让协程安全的启动,定义的扩展方法如下:

fun CoroutineScope.safeLaunch(
    onError: ((Throwable) -> Unit)? = null,
    onLaunchBlock: () -> Unit
) {
    val exceptionHandler = CoroutineExceptionHandler { _, throwable ->
        onError?.invoke(throwable)
    }

    this.launch(exceptionHandler) {
        onLaunchBlock.invoke()
    }
}

我们就能安全的启动协程啦,使用如下:

 GlobalScope.safeLaunch(onError = { throwable ->

   YYLogUtils.w(throwable.message ?: "UnKnow Error")

 })  {

   YYLogUtils.w("执行在协程中...")

    delay(1000L)

    val num = 999/0

    YYLogUtils.w("执行完毕...")
}

当然了这只是最简单的封装,在实际开发中,我们应该还需要传入协程上下文等参数继续丰富这个扩展函数。

五、组合协程上下文

这么多好用的功能,我看每次都只能传入一个上下文对象,我能不能都要?答案是可以的。

通过CoroutineContext.plus方法,我们可以组合定义多个上下文元素,我们可以使用 + 操作符来实现。

例如我把上面的几个上下文都组合起来看看

var job: Job = Job()

private val exceptionHandler = CoroutineExceptionHandler { coroutineContext, throwable ->
    YYLogUtils.e(throwable.message ?: "Unkown Error")
}

 GlobalScope.launch(Dispatchers.Main + job + exceptionHandler + CoroutineName("job") 
 + Executors.newSingleThreadExecutor().asCoroutineDispatcher()) {

   YYLogUtils.w("执行在协程中...")

    delay(1000L)

    val num = 999/0

    YYLogUtils.w("执行完毕...")
}

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

Ok,这样 + 起来,我们就可以实现全部的功能全部集成在一起,是不是很方便呢。

总结

En el último artículo, hablamos sobre el uso básico de las corrutinas. Lo que dominamos son varias formas de iniciar corrutinas, varias formas de cambiar hilos, ejecución asíncrona y síncrona, y suspender funciones, conceptos de bloqueo y no bloqueo.

En este artículo, comprendemos además que el programador de subprocesos de conmutación se implementó originalmente en función del contexto de corrutina, y también dominamos varios procesos de contexto de corrutina de uso común.

En el próximo artículo, hablaremos sobre el concepto de alcance de rutina y cómo personalizar su propio alcance de rutina.

El concepto y el marco de las corrutinas son relativamente amplios. Si hay algún error o error en mi explicación, espero que los estudiantes puedan señalarlo y comunicarlo.

Si sientes que este artículo te ha inspirado un poco, espero que puedas 点赞apoyarme, tu apoyo es mi mayor motivación.

Ok, este es el final de este problema.

Estoy participando en el reclutamiento del programa de firma de creadores de la Comunidad Tecnológica de Nuggets, haga clic en el enlace para registrarse y enviar .

Supongo que te gusta

Origin juejin.im/post/7118529261954138149
Recomendado
Clasificación