第四节:协程取消和异常的运用

502 阅读2分钟

前言:

前几篇文章我们介绍了协程的基础知识、上下文、协程的取消和异常:

第一节:协程的基础知识

第二节:深入理解协程上下文

第三节:源码解析协程的取消和异常流程 + 图解

这篇文章我们主要是对上篇文章中的结论做一些验证。

1.子Job调用cancel()方法,不会取消父Job

fun main() {
    val scope = CoroutineScope(Dispatchers.Default)
    println("root Job: ${scope.coroutineContext[Job]}")
    val job = scope.launch {
        println("child Job: ${coroutineContext.job}\n")
    }
    job.cancel()
    runBlocking { delay(100) }

    println("root Job: ${scope.coroutineContext[Job]?.isActive}")
    println("child Job: ${job.isActive}")
}
// 输出
root Job: JobImpl{Active}@dc24521
child Job: StandaloneCoroutine{Cancelling}@347ba574

root Job: true
child Job: false

调用scope.launch()方法创建的Job是我们在CoroutineScope()方法中创建Job的子Job,当我们调用job.cancel()时,launch()函数中的Job已经取消了,但是它的父Job仍然处于活跃状态。Job的取消只是更改了Job的状态,并不能影响协程的执行流程。我们需要在适当的地方添加isActive字段来协作协程的取消。

2.父Job调用cancel()方法,会循环遍历取消子Job,并等待子Job执行完成后,再继续执行自己的取消流程

fun main() {
    val scope = CoroutineScope(Dispatchers.Default)
    println("parent Job: ${scope.coroutineContext[Job]}")
    val child1 = scope.launch {
        println("child1 Job: ${coroutineContext[Job]}")
    }
    val child2 = scope.launch {
        println("child2 Job: ${coroutineContext[Job]}")
    }
    scope.coroutineContext[Job]?.cancel()

    println("parent Job: ${scope.coroutineContext[Job]?.isActive}")
    println("child1 Job: ${child1.isActive}")
    println("child2 Job: ${child2.isActive}")
    runBlocking { delay(100) }
}
// 输出
parent Job: JobImpl{Active}@dc24521
child1 Job: StandaloneCoroutine{Active}@6ecd1d5d
child2 Job: StandaloneCoroutine{Active}@7524996b

parent Job: false
child1 Job: false
child2 Job: false

3.如果一个子Job抛异常了,那么默认情况下它会取消它的父Job,并且会通过childCancelled() 方法逐级向上传递,直到根Job

fun main() {
    val scope = CoroutineScope(Dispatchers.Default + CoroutineExceptionHandler{ _,e ->
        println("${e.message}")
    })

    var childJob:Job? = null
    val parentJob = scope.launch {
        childJob = launch { throw Exception("throw exception.\n") }
    }

    runBlocking { delay(100) }
    println("rootJob: ${scope.coroutineContext[Job]?.isActive}")
    println("parentJob: ${parentJob.isActive}")
    println("childJob: ${childJob?.isActive}")
}
// 输出
throw exception.

rootJob: false
parentJob: false
childJob: false

4.如果我们使用函数SupervisorJob()supervisorScope()来创建一个Job,如果它的子Job发生异常则不会影响到的它的父Job,原因是重写了childCancelled()的方法

使用SupervisorJob()

fun main() {
    val scope = CoroutineScope(SupervisorJob() + CoroutineExceptionHandler{ _, e ->
        println("${e.message}\n")
    })
    println("parent Job: ${scope.coroutineContext[Job]}")
    val childJob = scope.launch {
        throw Exception("child job throw exception")
    }

    runBlocking { delay(100) }
    println("parent Job: ${scope.coroutineContext[Job]?.isActive}")
    println("child Job: ${childJob.isActive}")
}
// 输出
parent Job: SupervisorJobImpl{Active}@5b275dab
child job throw exception

parent Job: true
child Job: false

使用作用域挂起函数supervisorScope()

fun main() {
    val scope = CoroutineScope(CoroutineExceptionHandler{ _, e ->
        println("${e.message}")
    })
    println("root Job: ${scope.coroutineContext[Job]}")

    var childJob:Job? = null
    val parentJob = scope.launch {
        println("parent Job: ${coroutineContext[Job]}")
        supervisorScope {
            launch {
                childJob = coroutineContext[Job]
                println("child Job: $childJob \n")
                throw Exception("child job throw exception\n")
            }
        }
    }
    runBlocking { delay(50) }
    println("root Job: ${scope.coroutineContext[Job]?.isActive}")
    println("parent Job: ${parentJob.isActive}")
    println("child Job: ${childJob?.isActive}")
}
// 输出
root Job: JobImpl{Active}@5b275dab
parent Job: StandaloneCoroutine{Active}@4e7cb0ad
child Job: StandaloneCoroutine{Active}@7d2af9b5 

child job throw exception

root Job: true
parent Job: false
child Job: false

5. 如果一个父Job的多个子Job都发生了异常,那么我们总是取第一个异常来作为父Job异常的原因

fun main() {
    val scope = CoroutineScope(SupervisorJob() + CoroutineExceptionHandler{ _, e ->
        println("${e.message}\n")
    })
    println("parent Job: ${scope.coroutineContext[Job]}")
    scope.launch {
        launch {
            throw Exception("child job1 throw exception")
        }

        launch {
            throw Exception("child job2 throw exception")
        }
    }
    runBlocking { delay(100) }
}
// 输出
parent Job: SupervisorJobImpl{Active}@5b275dab
child job1 throw exception

6.父Job可以调用cancelChildren()方法来取消它所有的子Job

fun main() {
    val scope = CoroutineScope(SupervisorJob() + CoroutineExceptionHandler { _, e ->
        println("${e.message}\n")
    })
    var childJob1:Job? = null
    var childJob2:Job? = null
    val parentJob = scope.launch {
        childJob1 = launch {
            println("child1 launch.")
            delay(100)
            println("child1 execute")
        }

        childJob2 = launch {
            println("child2 launch.")
            delay(100)
            println("child2 execute")
        }
    }
    runBlocking { delay(50) }
    parentJob.cancelChildren()
    println("childJob1: ${childJob1?.isActive}")
    println("childJob2: ${childJob2?.isActive}")

}
// 输出
child1 launch.
child2 launch.
childJob1: false
childJob2: false

总结:

这篇文章我们只是对上一节的结论做了验证。主要还是需要我们要掌握协程的取消和异常的执行流程,下篇文章将继续介绍协程中的另外一个知识Channel~