• kotlin coroutine源码解析之Job取消以及异常的处理流程分析


    Job取消cancel过程分析

    Job取消cancel过程非常复杂,尤其是多个父子Job嵌套,以及内部又拥有挂起函数的情况下,流程是相互依赖,相互等待的过程,所有这里只做最简单的Job情况,方便代码跟踪。

    还是从一段最简单的代码开始跟踪,代码如下:

    fun testJobTree() {
    
        val myScope = CoroutineScope(CoroutineName("name") + IO + Job() + CoroutineExceptionHandler{ _, t -> })
    
        val job = myScope.launch {
    		println("job has run to end")
        }
    
    	println("job has launch")
        job.cancel("cancel job0")
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    job的工作是打印一句话这个job的工作就完成了。在Job启动后,立刻调用cancel方法取消该Job。

    我们从cancel方法跟踪进去,得到的流程如下:
    在这里插入图片描述

    当我们调用cancel方法之后,会依次进入:
    cancel(cause: CancellationException?) -> cancelInternal(cause: Throwable) -> cancelImp(cause: Any?)
    从cancelImp开始具体分析 (cancelImp这个方法比较重要,很多地方会调用,子Job通知父Job取消的方法,也会有可能让父Job去调用这个取消方法,parentHandler.childCancelled() -> parent.cancelImp())。

    makeCancelling()代码如下:

       internal fun cancelImpl(cause: Any?): Boolean {
            var finalState: Any? = COMPLETING_ALREADY
            if (onCancelComplete) {
                // make sure it is completing, if cancelMakeCompleting returns state it means it had make it
                // completing and had recorded exception
                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
                }
            }
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    我们的代码不会出现上面判断的一个if (onCancelComplete)条件成立的情况,onCancelComplete一般都是false,这个判断直接跳过,会来到makeCancelling方法:

    cancel Job的过程中,job会存在以下四种状态:

    // COMPLETING_ALREADY -- when already complete or completing
    说明已经完成或者是正在完成
    
    // COMPLETING_RETRY -- when need to retry due to interference
    说明任务取消需要重试
    
     // COMPLETING_WAITING_CHILDREN -- when made completing and is waiting for children
     等待孩子Job结束,自己才能结束
    
    // final state -- when completed, for call to afterCompletion
    当结束后,等待调用afterCompletion回调通知
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    其中,如果Job处于Completing_Already或者是Completing_Waiting_children状态,多次取消cancel操作,将会被忽略,不会继续进行下面的取消的进一步操作。

    makeCancelling方法会有三个分叉:
    1.state是否是Finishing的状态,这种状态下直接调用:notifyCancelling(state.list, it)
    2.state是InComplete的状态,然后还要判断是否是isActive的状态,
    是活跃状态下,调用:tryMakeCancelling(state, causeException)
    非活跃状态下,调用:tryMakeCompleting(state, CompletedExceptionally(causeException))

    那么我们先分析第一种最简单的情况,notifyCancelling()方法,这个方法源码如下:

    	//JobSupport类
        private fun notifyCancelling(list: NodeList, cause: Throwable) {
            // first cancel our own children
            onCancelling(cause)
            notifyHandlers<JobCancellingNode>(list, cause)
            // then cancel parent
            cancelParent(cause) // tentative cancellation -- does not matter if there is no parent
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    第一个方法不重要,是需要子类去覆写的,是个空函数
    第二个方法是通知state的数据结构每个节点调用它的invoke方法,如果是ChildHandleNode节点的话,那么代理调用的是child.parentCancelled()方法,如果是其他节点的可能其他效果(这个节点类型后面再详解,有很多种);那么综上来看,这个方法就是父Job通知子Job,父Job已经取消了,子Job需要做对应的处理了、
    第三个方法是Job需要通知并取消父Job、

    
        //JobSupport类
        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) }
        }
    		
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    可以看到上面的方法里面,是遍历state.list的数组,调用每个节点的invoke方法,其中node如果是ChildHandlerNode类型的话,是对应的子Job节点,看下这个类的结构:

    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)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    触发的是invoke(cause: Throwable?) = childJob.parentCancelled(job),也就是通知子Job去取消。

    	//JobSupport类
    	private fun cancelParent(cause: Throwable): Boolean {
            // Is scoped coroutine -- don't propagate, will be rethrown
            if (isScopedCoroutine) return true
    
            /* CancellationException is considered "normal" and parent usually is not cancelled when child produces it.
             * This allow parent to cancel its children (normally) without being cancelled itself, unless
             * child crashes and produce some other exception during its completion.
             */
            val isCancellation = cause is CancellationException
            val parent = parentHandle
            // No parent -- ignore CE, report other exceptions.
            if (parent === null || parent === NonDisposableHandle) {
                return isCancellation
            }
    
            // Notify parent but don't forget to check cancellation
            return parent.childCancelled(cause) || isCancellation
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    cancelParent()方法是取消父Job的作用,首先判断这个错误是不是取消异常(除了取消异常,还要其他抛出的Jvm异常等等),如果parent是空的,或者parent已经没有关联了,那么返回isCancellation的bol值,如果不是,那么调用parent.childCancelled()方法,返回 结果与isCancellation的bool值。

    我们来看下childCancelled()方法:

    job的类型有两种,一个是JobSupport也就是最常用的Job,还有一种是supervisorJob,这个覆写了childCancelled方法。

    //JobSupport
    public open fun childCancelled(cause: Throwable): Boolean {
        if (cause is CancellationException) return true
        return cancelImpl(cause) && handlesException
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    上面JobSupport内,childCancelled方法,对于来自子Job的取消异常CancellationException是不会让父job取消的,直接返回true,说明消耗了事件但是不取消自己;而Job的其他异常是会调用父Job自己的cancelImp方法,也就是会让父Job取消。

    private class SupervisorJobImpl(parent: Job?) : JobImpl(parent) {
        override fun childCancelled(cause: Throwable): Boolean = false
    }
    
    • 1
    • 2
    • 3

    上面的supervisorJob是覆写了这个方法,直接返回false,也就是说,来自子Job的取消异常和其他异常都不会让父Job取消自己的,返回false代表父Job没有处理,不消耗这个通知事件,需要子Job自己去处理。

    通过这个方法可以看出来,父Job无论是job还是supervisorJob都是不处理cancelledException的,其他的异常的话,普通父Job会被取消,supervisorJob会无视这个异常不处理,抛回给子Job自己处理。

    我们来做个代码演示:

    val myScope = CoroutineScope(CoroutineName("name") + IO + Job() + CoroutineExceptionHandler{ c, e -> println(e.message)})
    
    val job = myScope.launch {
    
        var job2 : Job? = launch {
            println("job2 begin")
            delay(1000)
            println("job2 end")
        }
    
        var job3 : Job? = launch {
            println("job3 begin")
            delay(2000)
            println("job3 end")
        }
    
        job2?.cancel("cancel job")
        println("job has run to end")
    }
    
    println("job has launch")
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    按照刚刚分析的代码来看,job3的打印输出是不被job2的取消打断的,打印结果如下:

    2022-11-15 15:46:17.608  E/MainActivity: main : job has launch
    2022-11-15 15:46:17.611  E/MainActivity: DefaultDispatcher-worker-2 : job2 begin
    2022-11-15 15:46:17.613  E/MainActivity: DefaultDispatcher-worker-1 : job has run to end
    2022-11-15 15:46:17.614  E/MainActivity: DefaultDispatcher-worker-1 : job3 begin
    2022-11-15 15:46:19.619  E/MainActivity: DefaultDispatcher-worker-2 : job3 end
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    确实如此,除了Job2自己被取消了,其他Job都没有受到影响。job2是要delay去等待是为了协程可以去检测当前状态是否取消,sleep是线程级别的,协程是没有办法在这里去检查的。

    在看一个抛出异常的例子:

    val myScope = CoroutineScope(CoroutineName("name") + IO + Job() + CoroutineExceptionHandler{ c, e ->
                println(c[CoroutineName].toString() + e.message)
            })
    
    
            val job = myScope.launch {
    
                var job2 : Job? = launch {
                    println("job2 begin")
                    delay(100)
                    throw Exception("测试异常报错")
                    println("job2 end")
                }
    
                var job3 : Job? = launch {
                    println("job3 begin")
                    delay(2000)
                    println("job3 end")
                }
    
                println("job has run to end")
            }
    
            println("job has launch")
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    打印的结果是:

    2022-11-15 15:49:09.338  E/MainActivity: main : job has launch
    2022-11-15 15:49:09.342  E/MainActivity: DefaultDispatcher-worker-1 : job has run to end
    2022-11-15 15:49:09.343  E/MainActivity: DefaultDispatcher-worker-1 : job2 begin
    2022-11-15 15:49:09.345  E/MainActivity: DefaultDispatcher-worker-4 : job3 begin
    2022-11-15 15:49:09.448  E/MainActivity: DefaultDispatcher-worker-7 : CoroutineName(name)测试异常报错
    
    • 1
    • 2
    • 3
    • 4
    • 5

    可以看出来,job2,job3都启动成功了,但是随着job2的抛出异常,job2,job3的最后一条语句都没有打印,说明异常抛出的话,如果父Job是普通的Job,那么父Job会取消,父Job也会通知它的所有子Job取消。

    再来看一个supervisorJob的例子:

    val myScope = CoroutineScope(CoroutineName("name") + IO + Job() + CoroutineExceptionHandler{ c, e ->
       println("CoroutineScope exceptionHandler: " + c[CoroutineName].toString() + e.message)
    })
    
    val job = myScope.launch {
    
       var job2 : Job? = launch(SupervisorJob(coroutineContext[Job])) {
           println("job2 begin")
           delay(100)
           throw Exception("测试异常报错")
           println("job2 end")
       }
    
       var job3 : Job? = launch {
           println("job3 begin")
           delay(2000)
           println("job3 end")
       }
    
    
       println("job has run to end")
    }
    
    println("job has launch")
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24

    打印的结果如下:

    coroutineTestActivity: job has launch
    job2 begin
    coroutineTestActivity: job has run to end
    job3 begin
    coroutineTestActivity: CoroutineScope exceptionHandler: CoroutineName(name)测试异常报错
    job3 end
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    可以看到Job2虽然抛出了异常,但是Job3还是完整的跑完了,没有中断,说明supervisorJob起到作用了,上面分析了,SupervisorJob遇到子Job的childCancelled方法回调是不处理的,所以只会在内部自己处理异常。

    我们是在launch方法中传入了supervisorJob参数:

       var job2 : Job? = launch(SupervisorJob(coroutineContext[Job])) {
           println("job2 begin")
           delay(100)
           throw Exception("测试异常报错")
           println("job2 end")
       }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    看下SupervisorJob(coroutineContext[Job])这个构造函数做了什么:

    public fun SupervisorJob(parent: Job? = null) : CompletableJob = SupervisorJobImpl(parent)
    
    private class SupervisorJobImpl(parent: Job?) : JobImpl(parent) {
        override fun childCancelled(cause: Throwable): Boolean = false
    }
    
    internal open class JobImpl(parent: Job?) : JobSupport(true), CompletableJob {
        init { initParentJob(parent) } //这个函数上一章分析过,是关联父子Job的
    	//省略、、、
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    SupervisorJob -> SupervisorJobImpl -> JobImpl.initParentJob(parent)
    我们直接看initParentJob(parent) 这个方法,这个方法我们在上一章 解析launch启动过程已经分析过了,这个方法是让parent和本身Job进行父子关联。然后supervisorJob作为参数传入launch方法,那么supervisorJob又和Job2产生了父子关系。这样我们就知道了例子中的代码的Job树的结构是怎么样的了:
    在这里插入图片描述
    那么Job2如果抛出异常的话,这个异常会被supervisorJob忽略掉,其他Job不会收到影响。

    Job的异常处理

    job的异常处理是和上面一节内容相关的,我们已经分析过了,SupervisorJob不会处理来自子Job的异常情况,那么这个来自子Job的异常情况该由谁来处理呢?我们从fun finalizeFinishingState(state: Finishing, proposedUpdate: Any?): Any?这个方法开始分析:

    // Finalizes Finishing -> Completed (terminal state) transition.
    // ## IMPORTANT INVARIANT: Only one thread can be concurrently invoking this method.
    // Returns final state that was created and updated to
    private fun finalizeFinishingState(state: Finishing, proposedUpdate: Any?): Any? {
            /*
             * Note: proposed state can be Incomplete, e.g.
             * async {
             *     something.invokeOnCompletion {} // <- returns handle which implements Incomplete under the hood
             * }
             */
            //----------------------检查当前state的状态----------------------------
            assert { this.state === state } // consistency check -- it cannot change
            assert { !state.isSealed } // consistency check -- cannot be sealed yet
            assert { state.isCompleting } // consistency check -- must be marked as completing
            val proposedException = (proposedUpdate as? CompletedExceptionally)?.cause
            // Create the final exception and seal the state so that no more exceptions can be added
    
            //----------------------收集错误信息----------------------------------
            var wasCancelling = false // KLUDGE: we cannot have contract for our own expect fun synchronized
            val finalException = synchronized(state) {
                wasCancelling = state.isCancelling
                val exceptions = state.sealLocked(proposedException)
                val finalCause = getFinalRootCause(state, exceptions)
                if (finalCause != null) addSuppressedExceptions(finalCause, exceptions)
                finalCause
            }
            // Create the final state object
            val finalState = when {
                // was not cancelled (no exception) -> use proposed update value
                finalException == null -> proposedUpdate
                // small optimization when we can used proposeUpdate object as is on cancellation
                finalException === proposedException -> proposedUpdate
                // cancelled job final state
                else -> CompletedExceptionally(finalException)
            }
            
            //----------------------异常处理----------------------------------
    		// Now handle the final exception
            if (finalException != null) {
                val handled = cancelParent(finalException) || handleJobException(finalException)
                if (handled) (finalState as CompletedExceptionally).makeHandled()
            }
    
    
            // Process state updates for the final state before the state of the Job is actually set to the final state
            // to avoid races where outside observer may see the job in the final state, yet exception is not handled yet.
            if (!wasCancelling) onCancelling(finalException)
            onCompletionInternal(finalState)
            // Then CAS to completed state -> it must succeed
            val casSuccess = _state.compareAndSet(state, finalState.boxIncomplete())
            assert { casSuccess }
            // And process all post-completion actions
            completeStateFinalization(state, finalState)
            return finalState
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55

    我们直接看异常处理部分代码,其他部分代码可以暂时不用关心,先调用了cancelParent(finalException) 方法,根据上面的分析画出流程图:
    在这里插入图片描述
    最后异常会由Job自己进行处理,调用了handleJobException方法进行处理:

    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
        }
    }
    
    @InternalCoroutinesApi
    public fun handleCoroutineException(context: CoroutineContext, exception: Throwable) {
        // Invoke an exception handler from the context if present
        try {
            context[CoroutineExceptionHandler]?.let {
                it.handleException(context, exception)
                return
            }
        } catch (t: Throwable) {
            handleCoroutineExceptionImpl(context, handlerException(exception, t))
            return
        }
        // If a handler is not present in the context or an exception was thrown, fallback to the global handler
        handleCoroutineExceptionImpl(context, exception)
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26

    可以看到调用的是被StandaloneCoroutine覆写的handleJobException方法 -> handleCoroutineException方法,里面处理方式是:先取出这个协程的上下文集合中的ExceptionHandle来对异常进行处理,这个context定义如下:

        public final override val context: CoroutineContext = parentContext + this
    
    • 1

    launch的时候,继承自scopeCoroutine的上下文,然后用自己Job类型去覆盖集合中的元素。如果这个context[CoroutineExceptionHandler]不存在的话,那么下面还需要调用handleCoroutineExceptionImpl方法,代码如下:

    internal actual fun handleCoroutineExceptionImpl(context: CoroutineContext, exception: Throwable) {
        // use additional extension handlers
        for (handler in handlers) {
            try {
                handler.handleException(context, exception)
            } catch (t: Throwable) {
                // Use thread's handler if custom handler failed to handle exception
                val currentThread = Thread.currentThread()
                currentThread.uncaughtExceptionHandler.uncaughtException(currentThread, handlerException(exception, t))
            }
        }
    
        // use thread's handler
        val currentThread = Thread.currentThread()
        // addSuppressed is never user-defined and cannot normally throw with the only exception being OOM
        // we do ignore that just in case to definitely deliver the exception
        runCatching { exception.addSuppressed(DiagnosticCoroutineContextException(context)) }
        currentThread.uncaughtExceptionHandler.uncaughtException(currentThread, exception)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    遍历一下已经添加到handler列表里面的异常处理器用来处理异常,如果没有会调用当前线程的异常处理器,这部分大概就是这样。

    在supervisorJob的例子中,我们发现Job2抛出异常后,异常被myScope异常处理器处理,说明Job2的上下文集合中继承了myScope集合的ExceptionHandler元素,因为supervisorJob只是和父Job关联了,并且launch方法从父Job那里继承元素集合,而我们的Job2是又和supervisorJob组成父子关系,又拥有ExceptionHandler,因此异常发生的时候会用继承过来的异常处理器进行异常处理,如果我们在launch的参数中添加ExceptionHandler话,运行下看下效果:

      var job2 : Job? = launch(SupervisorJob(coroutineContext[Job]) + CoroutineExceptionHandler{ c, e ->
                println("job2 exceptionHandler: " + c[CoroutineName].toString() + e.message)
            }) {
          println("job2 begin")
          delay(100)
          throw Exception("测试异常报错")
          println("job2 end")
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    打印结果:

    2022-11-15 17:22:15.989 E/MainActivity: main : job has launch
    2022-11-15 17:22:15.995 E/MainActivity: DefaultDispatcher-worker-1 : job has run to end
    2022-11-15 17:22:15.996 E/MainActivity: DefaultDispatcher-worker-1 : job2 begin
    2022-11-15 17:22:15.998 E/MainActivity: DefaultDispatcher-worker-1 : job3 begin
    2022-11-15 17:22:16.099 E/MainActivity: DefaultDispatcher-worker-2 : job2 exceptionHandler: CoroutineName(name)测试异常报错
    2022-11-15 17:22:18.002 E/MainActivity: DefaultDispatcher-worker-2 : job3 end
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    可以看到Job2的异常被自己添加的job2 exceptionHandler处理了,因为i从父继承过来的ExceptionHandler被自己设置的ExceptionHandler覆盖掉了,其他的运行效果也不变。

    Job异常处理的传递链条

    首先分析如下代码:

    val myScope = CoroutineScope(CoroutineName("name") + IO + Job() + CoroutineExceptionHandler{ c, e ->
                println("CoroutineScope exceptionHandler: " + c[CoroutineName].toString() + e.message)
            })
    
    val job = myScope.launch(CoroutineExceptionHandler{ c, e ->
        println("job exceptionHandler: " + c[CoroutineName].toString() + e.message)
    }) {
    
        var job2 : Job? = launch(CoroutineExceptionHandler{ c, e ->
            println("job2 exceptionHandler: " + c[CoroutineName].toString() + e.message)
        }) {
            println("job2 begin")
            delay(100)
            throw Exception("测试异常报错")
            println("job2 end")
        }
    
        var job3 : Job? = launch {
            println("job3 begin")
            delay(2000)
            println("job3 end")
        }
    
    
        println("job has run to end")
    }
    
    println("job has launch")
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28

    打印的结果是:

    2022-11-15 17:29:27.926 E/MainActivity: main : job has launch
    2022-11-15 17:29:27.934 E/MainActivity: DefaultDispatcher-worker-1 : job has run to end
    2022-11-15 17:29:27.935 E/MainActivity: DefaultDispatcher-worker-1 : job2 begin
    2022-11-15 17:29:27.937 E/MainActivity: DefaultDispatcher-worker-3 : job3 begin
    2022-11-15 17:29:28.040 E/MainActivity: DefaultDispatcher-worker-2 : job exceptionHandler: CoroutineName(name)测试异常报错
    
    • 1
    • 2
    • 3
    • 4
    • 5

    可以看到,异常处理被Job处理了, 很好奇,为啥不是根部的scope的Job去处理的异常处理啊,卧槽,还得继续跟踪代码,关键还是这个函数fun finalizeFinishingState(state: Finishing, proposedUpdate: Any?): Any?的异常处理的部分:

     //JobSupport
     // Now handle the final exception
     if (finalException != null) {
         val handled = cancelParent(finalException) || handleJobException(finalException)
         if (handled) (finalState as CompletedExceptionally).makeHandled()
     }
     
     //JobSupport
     public open fun childCancelled(cause: Throwable): Boolean {
         if (cause is CancellationException) return true
         return cancelImpl(cause) && handlesException
     }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    首先会调用cancelParent(finalException) 尝试让父Job调用childCancelled方法处理异常,这个函数返回false,说明父Job不处理,就会让Job本身去处理异常,job 协程和myScope的Job组成了父子关系,进而会尝试让myScope的Job处理异常,但是myScope的JobhandlesException的字段是false,导致返回的值是false:

      internal open val handlesException: Boolean get() = true
      
      override val handlesException: Boolean = handlesException()
    
      @JsName("handlesExceptionF")
      private fun handlesException(): Boolean {
          var parentJob = (parentHandle as? ChildHandleNode)?.job ?: return false
          while (true) {
              if (parentJob.handlesException) return true
              parentJob = (parentJob.parentHandle as? ChildHandleNode)?.job ?: return false
          }
      }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    mySocpe的handlesException这个字段为啥是false呢?作为根Job,所以它没有父Job,(parentHandle as? ChildHandleNode)?.job ?: return false 它的parentHandle必然是空的,因此导致返回false。这样的话myScope的Job不处理异常,cancelParent(finalException) 函数返回false,导致只能job自己去处理异常调用了handleJobException(finalException)函数,最后进入了job 自己的异常处理器中。

    再举一个例子,让job协程不定义自己的异常处理器,看看是哪个处理了异常:

    val myScope = CoroutineScope(CoroutineName("name") + IO + Job() + CoroutineExceptionHandler{ c, e ->
                printlnM("CoroutineScope exceptionHandler: " + c[CoroutineName].toString() + e.message)
            })
    
    val job = myScope.launch {
    
        var job2 : Job? = launch(CoroutineExceptionHandler{ c, e ->
            printlnM("job2 exceptionHandler: " + c[CoroutineName].toString() + e.message)
        }) {
            printlnM("job2 begin")
            delay(100)
            throw Exception("测试异常报错")
            printlnM("job2 end")
        }
    
        var job3 : Job? = launch {
            printlnM("job3 begin")
            delay(2000)
            printlnM("job3 end")
        }
    
    
        printlnM("job has run to end")
    }
    
    printlnM("job has launch")
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    2022-11-15 23:15:12.989  E/coroutineTestActivity: job has launch
    2022-11-15 23:15:12.990  E/coroutineTestActivity: job has run to end
    2022-11-15 23:15:12.990  E/coroutineTestActivity: job3 begin
    2022-11-15 23:15:12.990  E/coroutineTestActivity: job2 begin
    2022-11-15 23:15:13.092  E/coroutineTestActivity: CoroutineScope exceptionHandler: CoroutineName(name)测试异常报错
    
    • 1
    • 2
    • 3
    • 4
    • 5

    可以看到是myScope exceptionHandler处理的异常,这是因为joblaunch的时候,继承的就是Scope的上下文元素集合,没有传入CoroutineExceptionHandler元素,所以myScope exceptionHandler也不会被覆盖掉,这样job在处理异常的时候使用的myScopeexceptionHandler来处理的异常。

    使用范例

    想让协程A,B异常互不影响的写法。

    • 正确姿势一:
    val scope = CoroutineScope(SupervisorJob() + CoroutineExceptionHandler { _, _ -> })
    scope.launch(CoroutineName("A")) {
        delay(10)
        throw RuntimeException()
    }
    scope.launch(CoroutineName("B")) {
        delay(100)
        Log.e("petterp", "正常执行,我不会收到影响")
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 正确姿势二:
    supervisorScope {
        launch(CoroutineName("A")) {
            printlnM("job2 begin")
            delay(100)
            throw Exception("测试异常报错")
            printlnM("job2 end")
        }
    
        launch(CoroutineName("B")) {
            printlnM("job3 begin")
            delay(2000)
            printlnM("job3 end")
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    上面代码的节点结构:在这里插入图片描述

    • 错误姿势:
    val scope = CoroutineScope(CoroutineExceptionHandler { _, _ -> })
    scope.launch(CoroutineName("job") + SupervisorJob()) {
        launch(CoroutineName("A")) {
            delay(10)
            throw RuntimeException()
        }
        launch(CoroutineName("B")) {
            delay(100)
            Log.e("petterp", "正常执行,我不会收到影响")
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    scope.launch方法会创建一个job,参数中的SupervisorJob只是覆盖context集合中的父job元素,当前的ob和SupervisorJob仍然会构成父子关系,因此子A和B不是直接和SupervisorJob连接的,异常传递还是会到达job,因此A和B异常会相互影响。
    上面节点结构:在这里插入图片描述

    总结

    1. 异常的传导链,从子Job向父Job传导:如果父Job是supervisorJob的话,将不做处理,需要子Job自己处理;如果父Job是JobSupport的话,异常还会继续向父Job的父Job传递,直到根部Job。所以捕获异常的话在根ScopeCoroutine里面设置就比较合适,对于有supervisorJob的情况,需要在supervisorJob的子Job中设置异常处理器,supervisorJob自己不会处理异常。

      异常的情况:
      在这里插入图片描述

    2. 对应协程取消操作,子Job取消,不论父Job是supportJob还是supervisorJob,父Job都不会处理cancelledException,父Job不受影响;子Job会主动取消自己以及自己的所有子Job。

      取消的情况:
      在这里插入图片描述

    异常的处理逻辑可以用职场的例子解释。假设职场的潜规则是,任何员工出错了,首要是要向上级报告,如果上级愿意处理你的错误,那员工就不用管了,如果上级将问题打回给员工,那错误就得由员工自己处理
    那么回到问题本身,Job就相当于一个好老板,子协程犯的错,它愿意处理,SupervisorJob就相当于一个严厉的老板,子协程自己犯的错,自己解决。

  • 相关阅读:
    培训考试系统如何满足个性化学习需求?
    Python 共享内存与 Qt c++ 程序进程之间通信
    地理数据可视化的神奇组合:Python和Geopandas
    05 数据操纵之插入数据 | OushuDB 数据库使用入门
    使用Vitis HLS生成 IP核 (verilog版和图形化版)
    Java --- JVM的执行引擎
    uniapp 微信小程序如何实现多个item列表的分享
    单线程与多线程使用场景
    宏集干货 | Panorama SCADA平台的警报通知功能配置详解
    【Vue】组件化编程
  • 原文地址:https://blog.csdn.net/u012345683/article/details/127860890