Kotlin コルーチンに関する注意事項: CoroutineScope はコルーチンを管理します

         CoroutineScope は、コルーチンの構造化された並行性を実現するための鍵です。CoroutineScope を使用すると、すべてのコルーチンを同じスコープで一括管理できます。

 

        CoroutineScope と構造化された同時実行性

        launch と async は、CoroutineScope の拡張機能として定義されています。launch を呼び出す前に 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
}

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
}

        なぜ拡張メソッドとして設計する必要があるのですか?

        

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 は 2 つのトップレベル コルーチンを作成し、コルーチン内で launch を使用してサブ コルーチンを作成します。最後に、コルーチンの外で 500 ミリ秒待機し、scope.cancel() が呼び出されます。その結果、以前に作成された 4 つのコルーチンはすべてキャンセルされます。

         親コルーチンは Scope に属し、子コルーチンは親コルーチンに属し、scope.cancel() が呼び出される限り、この 4 つのコルーチンはキャンセルされます。

        

        コルーチンを管理する CoroutineScope の機能は Job に由来します。

        親子コルーチン関係を確立するには? - CoroutineScope がジョブを通じてコルーチンを管理する方法。

        CoroutineScope はインターフェイスですが、なぜそのコンストラクターを呼び出して CoroutineScope オブジェクトを作成できるのでしょうか? 匿名の内部クラスを作成するために object キーワードを使用すべきではありませんか?

        CoroutineScope() の呼び出しはコンストラクターではなく、トップレベルの関数です。

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)

         Kotlin での関数名は、ほとんどの場合「キャメル ケース命名法」に従いますが、一部の特殊なケースではこの命名法に従わない場合があります。上記のトップレベル関数 CoroutineScope() は実際には特殊なケースです。これは通常のトップレベル関数ですが、その関数は「コンストラクター」であるためです。同様の使い方で、最上位関数 Job() もあります。

        Kotlin では、トップレベルの関数をコンストラクターとして使用する場合、最初の文字が大文字になります。

        CoroutineScope を作成するとき、受信した Context に Job が含まれている場合はそれを直接使用し、Job が含まれていない場合は新しい Job が作成されます。これは、すべての CoroutineScope オブジェクトの Context に Job オブジェクトが必要であることを意味します。コード内の CoroutineScope(Job()) を 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 は、コルーチンの抽象クラスを表す AbstractCoroutine のサブクラスです。さらに、initParentJob パラメーターがあり、これは true です。これは、コルーチンの作成後に、コルーチンの親子関係を初期化する必要があることを意味します。LazyStandaloneCoroutine は StandaloneCoroutine のサブクラスであり、その active パラメーターは false です。これは、遅延読み込み方式でコルーチンを作成することを意味します。

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 は JobSupport のサブクラスであり、init{} コード ブロックでは、initParentJob パラメータに従って、コルーチンの親子関係を初期化する必要があるかどうかが判断されます。initParentJob が true であるため、ここの initParentJob() メソッドは確実に実行され、そのパラメーターparentContext[Job] によって取り出されたジョブは、実際にはスコープ内のジョブです。

 initParentJob() メソッドは、その親クラス 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
}

上記のコードには、注目すべき 3 つの場所があります。分析してみましょう。

まず、渡された親が空かどうかを判断し、親が空の場合、現在のコルーチンには親ジョブがなく、コルーチンの親子関係を作成する必要がないことを意味します。

次に、親に対応するジョブが開始されていることを確認します。

parent.attachChild(this) は、現在のジョブを親の子ジョブとして追加します。これは実際には、コルーチンの親子関係を確立するためのキー コードです。

 コルーチンの「構造化キャンセル」はどうですか?

 コルーチンの構造化されたキャンセルは、本質的にイベントの配信です。

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

         CoroutineScope の cancel() メソッドは、基本的にその中で Job.cancel() を呼び出します。そして、このメソッドの具体的な実装は 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() は最終的に JobSupport の cancelImpl() メソッドを呼び出します。上記のコードでは、onCancelComplete は Boolean 型のメンバー プロパティです。実行する必要があるコルーチン本体があるかどうかにかかわらず、現在のジョブを表します。また、CoroutineScope のジョブは手動で作成され、コルーチン コードを実行する必要がないため、true になります。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() は tryMakeCompleting() メソッドを呼び出し、最終的には tryMakeCompletingSlowPath() で notifyCancelling() メソッドを呼び出します。したがって、これは最も重要なコードです。

private fun notifyCancelling(list: NodeList, cause: Throwable) {
    onCancelling(cause)
    notifyHandlers<JobCancellingNode>(list, cause)
    cancelParent(cause)
}
  •  子ジョブに通知
notifyHandlers<JobCancellingNode>(list, cause)
  • 親ジョブに通知
 cancelParent(cause)

 

サブジョブ プロセスに通知します。

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

 現在のジョブのサブジョブをトラバースし、キャンセルされた原因を渡すと、ここでの invoke() は最終的に ChildHandleNode の invoke() メソッドを呼び出します。


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

 ChildHandleNode の invoke() メソッドは、parentCancelled() メソッドを呼び出し、最終的に cancelImpl() メソッドを呼び出します。ジョブキャンセルの入力機能。これは、実際には再帰呼び出しを行うことと同じです。

親ジョブに通知するプロセス:


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
}

 この関数の戻り値は、親コルーチンが例外を処理したことを示すために true を返し、親コルーチンが例外を処理しなかったことを示すために false を返します。この設計パターンは、責任の連鎖に似ています。


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

 例外が CancellationException の場合、コルーチンは特別な処理を実行します。一般に、親コルーチンは子コルーチンからの取り消し例外を無視します。それが別の例外である場合、親コルーチンは子コルーチンのキャンセルに応答します。コードは引き続き cancelImpl() メソッドを再帰的に呼び出します。

おすすめ

転載: blog.csdn.net/zhangying1994/article/details/128377381