协程学习(七)协程简单的使用之父子协程的层级关系

在前几篇文章都是在介绍协程的作用域,也经常提及协程的父子关系,那么什么样的协程关系才是父子协程关系呢

先看如下代码

kotlin 复制代码
@JvmStatic
fun main(array: Array<String>){
    //顶级父协程
    runBlocking {
        //子协程1
        launch {
            delay(1000)
            println("launch 1 子协程执行完毕")
        }

        //子协程2
        launch {
            delay(2000)
            println("launch 2 子协程执行完毕")
        }
    }
}
结果:
launch 1 子协程执行完毕
launch 2 子协程执行完毕

Process finished with exit code 0

可以发现,在 runBlocking 协程作用域内,我们创建了2个新的协程,这两个协程都进行了 delay,虽然 runBlocking 没有等待 launch 1 与 launch 2 的执行完毕,但是结果都被打印出来了,这就证明 launch 1 与 launch 2 协程被 顶级父协程所管理,这种现象就是 父子协程关系,那么如何建立父子协程关系呢,

想要创建父子协程关系,子协程必须创建在父协程的作用域内,并且创建时不能重新指定 CoroutineContext ,否则 新创建的协程将构建自己的协程作用域,从而升级与外部协程并立成兄弟协程

为了验证上面这段话,现在将例子重新修改一下,为了让大家看得更直观,例子里面包含了一个完整版本,一个缩写版本

scss 复制代码
@JvmStatic
fun main(array: Array<String>){
    //顶级父协程
    runBlocking {
        //缩写版(由于第一个参数就是 context ,我们可以省略 context = )-->由于重新指定了 CoroutineContext ,升级为 runBlocking 的兄弟协程
        launch(Job()) {
            delay(1000)
            println("launch 1 协程执行完毕")
        }

        //由于重新指定了 CoroutineContext ,升级为 runBlocking 的兄弟协程
        launch(context = Job()) {
            delay(2000)
            println("launch 2 协程执行完毕")
        }
    }
}

结果:

Process finished with exit code 0

发现在重新指定了context 是Job()的情况下, launch 1 与 launch 2 都没有重新打印,那么是不是只要替换了 CoroutineContext 就肯定会出现何种情况呢

看看下面这种情况

scss 复制代码
@JvmStatic
fun main(array: Array<String>){
    //顶级父协程
    runBlocking(Dispatchers.Default){
        //子协程1
        launch(Dispatchers.IO) {
            delay(1000)
            println("launch 1 子协程执行完毕")
        }

        //子协程2
        launch(context = Dispatchers.IO) {
            delay(2000)
            println("launch 2 子协程执行完毕")
        }
    }
}
结果:
launch 1 子协程执行完毕
launch 2 子协程执行完毕

Process finished with exit code 0

指定 runBlocking 的context 是 Dispatchers.Default,重新指定子协程的context 是 Dispatchers.IO,所有协程的结果都能打印出来,这里还不能确定,我们现在让 runBlocking 在0.5s后取消,看看 launch 1 与 launch 2 的表现

scss 复制代码
@JvmStatic
fun main(array: Array<String>){
    //顶级父协程
    runBlocking(Dispatchers.Default){
        //子协程1
        launch(Dispatchers.IO) {
            delay(1000)
            println("launch 1 子协程执行完毕")
        }

        //子协程2
        launch(context = Dispatchers.IO) {
            delay(2000)
            println("launch 2 子协程执行完毕")
        }
        delay(500)
        cancel()
    }
}
结果:
Exception in thread "main" kotlinx.coroutines.JobCancellationException: BlockingCoroutine was cancelled; job="coroutine#1":BlockingCoroutine{Cancelled}@e2144e4
	at kotlinx.coroutines.JobSupport.cancel(JobSupport.kt:1555)
	at kotlinx.coroutines.CoroutineScopeKt.cancel(CoroutineScope.kt:287)
	at kotlinx.coroutines.CoroutineScopeKt.cancel$default(CoroutineScope.kt:285)
	at com.tsm.opencv.Tsm$main$1.invokeSuspend(Tsm.kt:43)
	at kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
	at kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:106)
	at kotlinx.coroutines.scheduling.CoroutineScheduler.runSafely(CoroutineScheduler.kt:584)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.executeTask(CoroutineScheduler.kt:793)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.runWorker(CoroutineScheduler.kt:697)
	at kotlinx.coroutines.scheduling.CoroutineScheduler$Worker.run(CoroutineScheduler.kt:684)

Process finished with exit code 1

可以看到父协程被取消 launch 1 与 launch 2 同时也被取消了,即可证明他们现在也是父子协程关系

再来看下面的例子

scss 复制代码
@JvmStatic
fun main(array: Array<String>){
    //顶级父协程
    runBlocking{
        println(Thread.currentThread().name)

        //子协程1
        launch {
            withContext(Dispatchers.IO){
                delay(1000)
                println(Thread.currentThread().name)
            }
            println("launch 1 子协程执行完毕")
        }

        //子协程2
        launch {
            withContext(Dispatchers.IO){
                delay(1000)
                println(Thread.currentThread().name)
            }
            println("launch 2 子协程执行完毕")
        }
    }
}
结果:
main @coroutine#1
DefaultDispatcher-worker-1 @coroutine#3
DefaultDispatcher-worker-3 @coroutine#2
launch 2 子协程执行完毕
launch 1 子协程执行完毕

Process finished with exit code 0

为什么已经在子协程中使用了 Dispatchers.IO ,但是 runBlocking 还是会等待 launch 1 与 launch 2 这两个协程呢, 我们先来一步一步分析 1: runBlocking 是在主线程中创建的,所以我们第一行打印的线程名称是 main

2: 我们使用 runBlocking 的作用域 CoroutineScope 创建了子协程 launch 1 与 launch 2

3:我们调用 withContext 方法,并指定了 Dispatchers.IO ,注意 withContext 是一个方法,并不是重新创建一个协程,

所以建立父子协程关系的条件就是 子协程使用外部协程的作用域 CoroutineScope ,并且不重写 CoroutineContext , 但是从上面的例子中发现有一个特殊的,那就是 父协程 CoroutineContext 只是 Dispatchers.Default ,而子协程只使用 Dispatchers.IO 后,他们仍然是父子协程关系

为了证明如果父协程中除了指定 Dispatchers.Default外,增加其他参数条件就不成立,我又修改了一下这个案例

kotlin 复制代码
 @JvmStatic
fun main(array: Array<String>){
         runBlocking {
             var error=CoroutineExceptionHandler { _, e ->
                 println("e:$e")
             }
             var name=CoroutineName("tsm")
             var context= name + Dispatchers.IO + error
             //由于使用了新的 CoroutineContext 升级成为runBlocking的兄弟协程,  内部launch父协程
             CoroutineScope(context).launch {
                 launch(name+Dispatchers.Default+error) {
                     //子协程
                     delay(3000)
                 }
             }
             delay(2000)
         }
 }
 结果:
 Process finished with exit code 0

了解了协程作用域,协程父子关系,协程CoroutineContext的传播机制,我们就可以去了解协程的异常了,由于协程异常的特殊性,所有需要非常多的铺垫

相关推荐
HerayChen30 分钟前
HbuildderX运行到手机或模拟器的Android App基座识别不到设备 mac
android·macos·智能手机
顾北川_野31 分钟前
Android 手机设备的OEM-unlock解锁 和 adb push文件
android·java
hairenjing112333 分钟前
在 Android 手机上从SD 卡恢复数据的 6 个有效应用程序
android·人工智能·windows·macos·智能手机
小黄人软件1 小时前
android浏览器源码 可输入地址或关键词搜索 android studio 2024 可开发可改地址
android·ide·android studio
dj15402252031 小时前
group_concat配置影响程序出bug
android·bug
周全全2 小时前
MySQL报错解决:The user specified as a definer (‘root‘@‘%‘) does not exist
android·数据库·mysql
- 羊羊不超越 -2 小时前
App渠道来源追踪方案全面分析(iOS/Android/鸿蒙)
android·ios·harmonyos
wk灬丨3 小时前
Android Kotlin Flow 冷流 热流
android·kotlin·flow
千雅爸爸3 小时前
Android MVVM demo(使用DataBinding,LiveData,Fresco,RecyclerView,Room,ViewModel 完成)
android