Notas de corrotina Kotlin: CoroutineScope gerencia corrotinas

         CoroutineScope é a chave para realizar a simultaneidade estruturada de corrotinas. Usando o CoroutineScope, você pode gerenciar em lote todas as corrotinas no mesmo escopo.

 

        CoroutineScope e simultaneidade estruturada

        launch e async são definidos como funções de extensão do CoroutineScope. Um CoroutineScope deve ser adquirido antes de chamar launch.

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
}

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
}

        Por que ele deve ser projetado como um método de extensão?

        

fun main() {
    testCoroutinueScope()
}

private fun showScopeLog(any: Any?) {
    println("""$any Thread:${Thread.currentThread().name}""".trimIndent())
}

private fun testCoroutinueScope() {
    val scope = CoroutineScope(Job())
    scope.launch {
        launch {
            delay(1000000L)
            showScopeLog("Inner")
        }
        showScopeLog("Hello")
        delay(1000000L)
        showScopeLog("World") //不执行
    }

    scope.launch {
        launch {
            delay(1000000L)
            showScopeLog("Inner!!!")
        }
        showScopeLog("Hello!!!")
        delay(1000000L)
        showScopeLog("World!!!") //不执行
    }
    Thread.sleep(500L)
    scope.cancel()
}



Log:

Hello Thread:DefaultDispatcher-worker-1 @coroutine#1
Hello!!! Thread:DefaultDispatcher-worker-3 @coroutine#2

Process finished with exit code 0

 scope cria duas co-rotinas de nível superior e, dentro da co-rotina, usamos launch para criar uma sub-co-rotina. Finalmente, 500 milissegundos são esperados fora da co-rotina e scope.cancel() é chamado. Como resultado, as quatro co-rotinas criadas anteriormente são todas canceladas.

         A corrotina pai pertence a Scope e a corrotina filha pertence à corrotina pai. Enquanto scope.cancel() for chamado, essas 4 corrotinas serão canceladas.

        

        A capacidade do CoroutineScope de gerenciar corrotinas vem de Job.

        Como estabelecer a relação de co-rotina pai-filho? - Como o CoroutineScope gerencia corrotinas por meio do Job.

        CoroutineScope é uma interface, por que seu construtor pode ser chamado para criar um objeto CoroutineScope? A palavra-chave object não deveria ser usada para criar classes internas anônimas?

        Chamar CoroutineScope() não é um construtor, mas uma função de nível superior.

private fun testCoroutinueScope() {
    val scope = CoroutineScope(Job())
    scope.launch {
        launch {
            delay(1000000L)
            showScopeLog("Inner")
        }
        showScopeLog("Hello")
        delay(1000000L)
        showScopeLog("World") //不执行
    }

    scope.launch {
        launch {
            delay(1000000L)
            showScopeLog("Inner!!!")
        }
        showScopeLog("Hello!!!")
        delay(1000000L)
        showScopeLog("World!!!") //不执行
    }
    Thread.sleep(500L)
    scope.cancel()
}

public fun CoroutineScope(context: CoroutineContext): CoroutineScope =
    ContextScope(if (context[Job] != null) context else context + Job())
internal class ContextScope(context: CoroutineContext) : CoroutineScope {
    override val coroutineContext: CoroutineContext = context
    // CoroutineScope is used intentionally for user-friendly representation
    override fun toString(): String = "CoroutineScope(coroutineContext=$coroutineContext)"
}
public fun Job(parent: Job? = null): CompletableJob = JobImpl(parent)

         Os nomes das funções em Kotlin seguem o "método de nomenclatura camel case" na maioria dos casos, mas não seguem esse método de nomenclatura em alguns casos especiais. A função de nível superior CoroutineScope() acima é na verdade um caso especial, porque embora seja uma função de nível superior comum, sua função é um "construtor". Uso semelhante, há também a função de nível superior Job().

        Em Kotlin, quando uma função de nível superior é usada como construtor, a primeira letra é maiúscula.

        Ao criar CoroutineScope, se o Context de entrada contiver Job, use-o diretamente; se não contiver Job, um novo Job será criado. Isso significa que todo objeto CoroutineScope deve ter um objeto Job em seu Context. Não há problema em alterar CoroutineScope(Job()) no código para CoroutineScope().

        

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
}



private open class StandaloneCoroutine(
    parentContext: CoroutineContext,
    active: Boolean
) : AbstractCoroutine<Unit>(parentContext, initParentJob = true, active = active) {
    override fun handleJobException(exception: Throwable): Boolean {
        handleCoroutineException(context, exception)
        return true
    }
}

private class LazyStandaloneCoroutine(
    parentContext: CoroutineContext,
    block: suspend CoroutineScope.() -> Unit
) : StandaloneCoroutine(parentContext, active = false) {
    private val continuation = block.createCoroutineUnintercepted(this, this)

    override fun onStart() {
        continuation.startCoroutineCancellable(this)
    }
}

StandaloneCoroutine é uma subclasse de AbstractCoroutine, que representa a classe abstrata de corrotinas. Além disso, há um parâmetro initParentJob, que é verdadeiro, o que significa que após a criação da co-rotina, o relacionamento pai-filho da co-rotina precisa ser inicializado. LazyStandaloneCoroutine é uma subclasse de StandaloneCoroutine e seu parâmetro ativo é false, o que significa criar uma co-rotina de maneira lenta.

public abstract class AbstractCoroutine<in T>(
    parentContext: CoroutineContext,
    initParentJob: Boolean,
    active: Boolean
) : JobSupport(active), Job, Continuation<T>, CoroutineScope {
    ... ...
 init {
        /*
         * Setup parent-child relationship between the parent in the context and the current coroutine.
         * It may cause this coroutine to become _cancelling_ if the parent is already cancelled.
         * It is dangerous to install parent-child relationship here if the coroutine class
         * operates its state from within onCancelled or onCancelling
         * (with exceptions for rx integrations that can't have any parent)
         */
        if (initParentJob) initParentJob(parentContext[Job])
    }
}

AbstractCoroutine é uma subclasse de JobSupport. No bloco de código init{}, de acordo com o parâmetro initParentJob, é avaliado se o relacionamento pai-filho da corrotina precisa ser inicializado. initParentJob é true, então o método initParentJob() aqui definitivamente será executado, e o Job retirado por seu parâmetro parentContext[Job] é na verdade o Job no Scope.

 O método initParentJob() é um método em sua classe pai JobSupport.




public open class JobSupport constructor(active: Boolean) : Job, ChildJob, ParentJob, SelectClause0 {
    final override val key: CoroutineContext.Key<*> get() = Job

    protected fun initParentJob(parent: Job?) {
        assert { parentHandle == null }
        
        if (parent == null) {
            parentHandle = NonDisposableHandle
            return
        }
        
        parent.start()
        @Suppress("DEPRECATION")
        
        val handle = parent.attachChild(this)
        parentHandle = handle

        if (isCompleted) {
            handle.dispose()
            parentHandle = NonDisposableHandle 
        }
    }
}


public interface Job : CoroutineContext.Element {
    public val children: Sequence<Job>   
    public fun attachChild(child: ChildJob): ChildHandle
}

Existem três pontos a serem observados no código acima, vamos analisá-lo:

Primeiro, julgue se o pai transmitido está vazio. Se o pai estiver vazio, isso significa que a co-rotina atual não tem um trabalho pai e não há necessidade de criar um relacionamento pai-filho da co-rotina.

Em seguida, certifique-se de que o trabalho correspondente ao pai foi iniciado.

parent.attachChild(this) adicionará o trabalho atual como um trabalho filho do pai. Na verdade, esse é o código-chave para estabelecer o relacionamento pai-filho da co-rotina.

 Como é a corrotina "cancelamento estruturado"?

 O cancelamento estruturado de corrotinas é essencialmente a entrega de eventos.

public fun CoroutineScope.cancel(cause: CancellationException? = null) {
    val job = coroutineContext[Job] ?: error("Scope cannot be cancelled because it does not have a job: $this")
    job.cancel(cause)
}

         O método cancel() de CoroutineScope basicamente chama Job.cancel() nele. E a implementação específica desse método está no JobSupport




public override fun cancel(cause: CancellationException?) {
    cancelInternal(cause ?: defaultCancellationException())
}

public open fun cancelInternal(cause: Throwable) {
    cancelImpl(cause)
}

internal fun cancelImpl(cause: Any?): Boolean {
    var finalState: Any? = COMPLETING_ALREADY
    if (onCancelComplete) {
        finalState = cancelMakeCompleting(cause)
        if (finalState === COMPLETING_WAITING_CHILDREN) return true
    }
    if (finalState === COMPLETING_ALREADY) {
        finalState = makeCancelling(cause)
    }
    return when {
        finalState === COMPLETING_ALREADY -> true
        finalState === COMPLETING_WAITING_CHILDREN -> true
        finalState === TOO_LATE_TO_CANCEL -> false
        else -> {
            afterCompletion(finalState)
            true
        }
    }
}
if (onCancelComplete) {        
        finalState = cancelMakeCompleting(cause)        
        if (finalState === COMPLETING_WAITING_CHILDREN) 
        return true   
}

job.cancel() eventualmente chama o método cancelImpl() do JobSupport. No código acima, onCancelComplete é uma propriedade de membro do tipo booleano. Representa o Job atual, se existe um corpo de co-rotina que precisa ser executado. Além disso, como o trabalho no CoroutineScope é criado manualmente e não precisa executar nenhum código de corrotina, ele será verdadeiro. Continue analisando o método cancelMakeCompleting():

private fun cancelMakeCompleting(cause: Any?): Any? {
    loopOnState { state ->
        
        val finalState = tryMakeCompleting(state, proposedUpdate)
        if (finalState !== COMPLETING_RETRY) return finalState
    }
}

private fun tryMakeCompleting(state: Any?, proposedUpdate: Any?): Any? {
    if (state !is Incomplete)
        return COMPLETING_ALREADY

       
        return COMPLETING_RETRY
    }

    return tryMakeCompletingSlowPath(state, proposedUpdate)
}

private fun tryMakeCompletingSlowPath(state: Incomplete, proposedUpdate: Any?): Any? {
    
    notifyRootCause?.let { notifyCancelling(list, it) }

    return finalizeFinishingState(finishing, proposedUpdate)
}

 cancelMakeCompleting() chamará o método tryMakeCompleting(), que eventualmente chamará o método notifyCancelling() em tryMakeCompletingSlowPath(). Portanto, é o código mais crítico.

private fun notifyCancelling(list: NodeList, cause: Throwable) {
    onCancelling(cause)
    notifyHandlers<JobCancellingNode>(list, cause)
    cancelParent(cause)
}
  •  Notificar trabalho filho
notifyHandlers<JobCancellingNode>(list, cause)
  • Notificar trabalho pai
 cancelParent(cause)

 

Notifique o processo do sub-trabalho:

private inline fun <reified T: JobNode> notifyHandlers(list: NodeList, cause: Throwable?) {
    var exception: Throwable? = null
    list.forEach<T> { node ->
        try {
            node.invoke(cause)
        } catch (ex: Throwable) {
            exception?.apply { addSuppressedThrowable(ex) } ?: run {
                exception =  CompletionHandlerException("Exception in completion handler $node for $this", ex)
            }
        }
    }
    exception?.let { handleOnCompletionException(it) }
}

 Atravessando as subtarefas da tarefa atual e passando a causa cancelada, o invoke() aqui eventualmente chamará o método invoke() de ChildHandleNode:


internal class ChildHandleNode(
    @JvmField val childJob: ChildJob
) : JobCancellingNode(), ChildHandle {
    override val parent: Job get() = job
    override fun invoke(cause: Throwable?) = childJob.parentCancelled(job)
    override fun childCancelled(cause: Throwable): Boolean = job.childCancelled(cause)
}

public final override fun parentCancelled(parentJob: ParentJob) {
    cancelImpl(parentJob)
}

 O método invoke() de ChildHandleNode chama o método parentCancelled(), que eventualmente chama o método cancelImpl(). A função de entrada para cancelamento de trabalho. Isso é realmente equivalente a fazer uma chamada recursiva.

O processo de notificação do trabalho pai:


private fun cancelParent(cause: Throwable): Boolean {
    if (isScopedCoroutine) return true

    val isCancellation = cause is CancellationException
    val parent = parentHandle

    if (parent === null || parent === NonDisposableHandle) {
        return isCancellation
    }
    
    return parent.childCancelled(cause) || isCancellation
}

 O valor de retorno dessa função retorna true para indicar que a corrotina pai tratou da exceção e retorna false para indicar que a corrotina pai não tratou a exceção. Esse padrão de design é semelhante à cadeia de responsabilidade.


public open fun childCancelled(cause: Throwable): Boolean {
    if (cause is CancellationException) return true
    return cancelImpl(cause) && handlesException
}

 Quando a exceção for CancellationException, a co-rotina executará um tratamento especial. Em geral, as corrotinas pai ignoram as exceções de cancelamento das corrotinas filhas. E se for outra exceção, a corrotina pai responderá ao cancelamento da corrotina filha. O código continuará chamando o método cancelImpl() recursivamente novamente.

Acho que você gosta

Origin blog.csdn.net/zhangying1994/article/details/128377381
Recomendado
Clasificación