Kotlin协程代码执行顺序

2022-12-24 有错帮忙留言,一块完善进步

版本说明

2022-12-24 V1中五个结论是初步整理的五个结论
结论一. 协程是一个线程框架,协程依附于线程或者说在线程中执行
结论二. 一个线程中可以启动多个协程,启动的协程不一定运行在该线程
结论三. 一个线程中可以执行多个协程,同一线程的不同协程按照启动先后顺序执行代码,依附不同线程的协程间执行顺序不固定(并发)
结论四. 线程执行到协程的挂起函数时,会暂停该协程的往下执行,此时线程会去执行该线程中的其他协程代码,这个就是"非阻塞式"的含义
结论五. suspend关键字的含义是告诉线程,我这个函数的协程先暂停执行,我要先去执行函数体代码,你可以先执行其他协程代码,我稍后回来你再执行我的协程,
       用withContext挂起函数时遇到把函数中代码将要执行的线程指定为原线程时,则原线程会"置之不理"函数及函数所在协程,协程会一直被挂起,不会再恢复,引起了ANR
       
2022-12-28 V2结论基于V1结论,从"什么是协程","协程与线程关系","协程代码执行顺序","为啥要有挂起函数"四个角度重新归纳了四个结论
结论一. 协程是什么?
       协程是一个线程框架,协程依附于线程或者说在线程中运行
结论二. 协程与线程关系?
       一个线程可以启动并执行多个协程,一个协程可以依附多个线程或者说一个协程中的代码可以运行在不同线程
结论三. 协程代码执行顺序?
       同一线程的不同协程按照启动先后顺序执行代码,不同线程的不同协程并发执行(线程相同,协程不同、线程不同,协程不同)
       同一协程的不同线程代码按照顺序执行,同一协程相同线程代码按照顺序执行(线程相同,协程相同、线程不同,协程相同)
结论四. 为啥要有挂起函数?
       一个线程执行多个协程,在不同协程间切换,依赖的是官方库定义的一些挂起函数,
       挂起函数是suspend修饰的函数,是协程的一部分,当线程执行到协程的挂起函数时
       1. 若挂起函数内部,有切换线程操作,则线程会切去执行其他协程,协程会在新线程中执行,例如挂起函数withContext,delay,原线程并未阻塞这个就是非阻塞式的含义
       2. 若挂起函数内部,无切换线程操作,则线程会继续执行当前协程,协程仍在原线程中执行,例如挂起函数coroutineScope,supervisorScope
       3. suspend关键字只是一个标志,表明函数所在协程"可能"会被挂起,诱发线程切换去执行其他协程的标志,至于会不会被挂起,参考1、2中所述

V1的五个结论 2022-12-24

结论一. 协程是一个线程框架,协程依附于线程或者说在线程中执行
结论二. 一个线程中可以启动多个协程,启动的协程不一定运行在该线程
结论三. 一个线程中可以执行多个协程,同一线程的不同协程按照启动先后顺序执行代码,依附不同线程的协程间执行顺序不固定(并发)
结论四. 线程执行到协程的挂起函数时,会暂停该协程的往下执行,此时线程会去执行该线程中的其他协程代码,这个就是"非阻塞式"的含义
结论五. suspend关键字的含义是告诉线程,我这个函数的协程先暂停执行,我要先去执行函数体代码,你可以先执行其他协程代码,我稍后回来你再执行我的协程,
       用withContext挂起函数时遇到把函数中代码将要执行的线程指定为原线程时,则原线程会"置之不理"函数及函数所在协程,协程会一直被挂起,不会再恢复,引起了ANR      

验证V1结论二与结论三

测试代码
class TestActivity : Activity() {
    
    
    override fun onCreate(savedInstanceState: Bundle?) {
    
    
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        runBlocking {
    
    
            log("协程A--我是runBlocking启动的协程代码,我运行在主线程")
            launch {
    
     log("协程B--我是launch启动的协程代码,我运行在主线程") }
            launch {
    
     log("协程C--我是launch启动的协程代码,我运行在主线程") }
            launch(Dispatchers.IO) {
    
     log("协程D--我是launch启动的协程代码,我运行在IO线程") }
            log("协程A--我是runBlocking启动的协程代码,我也运行在主线程")
        }
    }
}
执行结果
第一次执行结果
    ThreadHash[540585569] Thread[main,5,main] 协程A--我是runBlocking启动的协程代码,我运行在主线程
    ThreadHash[540585569] Thread[main,5,main] 协程A--我是runBlocking启动的协程代码,我也运行在主线程
    ThreadHash[540585569] Thread[main,5,main] 协程B--我是launch启动的协程代码,我运行在主线程
    ThreadHash[540585569] Thread[main,5,main] 协程C--我是launch启动的协程代码,我运行在主线程
    ThreadHash[1632014539] Thread[DefaultDispatcher-worker-1,5,main] 协程D--我是launch启动的协程代码,我运行在IO线程
第二次执行结果
    ThreadHash[540585569] Thread[main,5,main] 协程A--我是runBlocking启动的协程代码,我运行在主线程
    ThreadHash[540585569] Thread[main,5,main] 协程A--我是runBlocking启动的协程代码,我也运行在主线程
    ThreadHash[1346142757] Thread[DefaultDispatcher-worker-1,5,main] 协程D--我是launch启动的协程代码,我运行在IO线程
    ThreadHash[540585569] Thread[main,5,main] 协程B--我是launch启动的协程代码,我运行在主线程
    ThreadHash[540585569] Thread[main,5,main] 协程C--我是launch启动的协程代码,我运行在主线程
结果分析
    主线程中启动了A,B,C,D四个协程,协程D运行在IO线程
    主线程中执行A,B,C三个协程时,按照A,B,C的启动顺序执行,协程D则跟其他3个协程并发执行

    由此得出结论二与结论三

验证V1结论四

测试代码
class TestActivity : Activity() {
    
    
    override fun onCreate(savedInstanceState: Bundle?) {
    
    
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        runBlocking {
    
    
            log("协程A--我是runBlocking启动的协程代码,我运行在主线程")
            launch {
    
    
                log("协程B--主线程,开始计算")
                var count = 0
                repeat(1000000000) {
    
    
                    if (it == 500000000) {
    
    
                        log("协程B--主线程,遇到了协程B中挂起函数delay,暂停该协程执行其他协程")
                        delay(500)
                        log("协程B--主线程,协程delay结束,该协程继续执行")
                    }
                    count++
                }
                count.also {
    
    
                    log("协程B--主线程,结束计算")
                }
            }
            launch {
    
    
                log("协程C--主线程,开始计算")
                var count = 0
                repeat(1000000000) {
    
    
                    if (it == 500000000) {
    
    
                        log("协程C--主线程,遇到了协程C中挂起函数delay,暂停该协程执行其他协程")
                        delay(500)
                        log("协程C--主线程,协程delay结束,该协程继续执行")
                    }
                    count++
                }
                count.also {
    
    
                    log("协程C--主线程,结束计算")
                }
            }
            log("协程A--我是runBlocking启动的协程代码,我也运行在主线程")
        }
    }
}
执行结果
16:47:30.516 System.out: ThreadHash[107566656] Thread[main,5,main] 协程A--我是runBlocking启动的协程代码,我运行在主线程
16:47:30.524 System.out: ThreadHash[107566656] Thread[main,5,main] 协程A--我是runBlocking启动的协程代码,我也运行在主线程
16:47:30.525 System.out: ThreadHash[107566656] Thread[main,5,main] 协程B--主线程,开始计算
16:47:36.949 System.out: ThreadHash[107566656] Thread[main,5,main] 协程B--主线程,遇到了协程B中挂起函数delay,暂停该协程执行其他协程
16:47:36.958 System.out: ThreadHash[107566656] Thread[main,5,main] 协程C--主线程,开始计算
16:47:43.384 System.out: ThreadHash[107566656] Thread[main,5,main] 协程C--主线程,遇到了协程C中挂起函数delay,暂停该协程执行其他协程
16:47:43.385 System.out: ThreadHash[107566656] Thread[main,5,main] 协程B--主线程,协程delay结束,该协程继续执行
16:47:49.807 System.out: ThreadHash[107566656] Thread[main,5,main] 协程B--主线程,结束计算
16:47:49.808 System.out: ThreadHash[107566656] Thread[main,5,main] 协程C--主线程,协程delay结束,该协程继续执行
16:47:56.229 System.out: ThreadHash[107566656] Thread[main,5,main] 协程C--主线程,结束计算
结果分析
A,B,C三个协程都是主线程协程,先打印了协程A的日志,然后执行协程B,执行过程中遇到挂起函数,协程B被挂起暂停,
此时线程开始执行协程C中代码,执行过程中再遇挂起函数,此时协程B的delay早已经超时,线程继续执行协程B代码,
协程B执行完后继续执行协程C剩余代码

由此得出结论四

验证V1结论五

测试代码
用例一:
class TestActivity : Activity() {
    
    
    override fun onCreate(savedInstanceState: Bundle?) {
    
    
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        runBlocking {
    
    
            log("协程A--我是runBlocking启动的协程代码,我运行在主线程")
            withContext(Dispatchers.IO) {
    
    
                log("我是被挂起函数withContext指定在IO线程进行的任务,挂起暂停协程A开始")
                var count = 0
                repeat(1000000000) {
    
    
                    count++
                }
                log("我是被挂起函数withContext指定在IO线程进行的任务,挂起暂停协程A结束")
            }
            launch {
    
     log("协程B--我是launch启动的协程代码,我运行在主线程") }
            launch {
    
     log("协程C--我是launch启动的协程代码,我运行在主线程") }
            log("协程A--我是runBlocking启动的协程代码,我也运行在主线程")
        }
    }
}
用例二:
class TestActivity : Activity() {
    
    
    override fun onCreate(savedInstanceState: Bundle?) {
    
    
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        runBlocking {
    
    
            log("协程A--我是runBlocking启动的协程代码,我运行在主线程")
            withContext(Dispatchers.Main) {
    
    
                log("我是被挂起函数withContext指定在Main线程进行的任务,挂起暂停协程A开始")
                var count = 0
                repeat(1000000000) {
    
    
                    count++
                }
                log("我是被挂起函数withContext指定在Main线程进行的任务,挂起暂停协程A结束")
            }
            launch {
    
     log("协程B--我是launch启动的协程代码,我运行在主线程") }
            launch {
    
     log("协程C--我是launch启动的协程代码,我运行在主线程") }
            log("协程A--我是runBlocking启动的协程代码,我也运行在主线程")
        }
    }
}
执行结果
用例一执行结果
17:07:20.334 System.out: ThreadHash[107566656] Thread[main,5,main] 协程A--我是runBlocking启动的协程代码,我运行在主线程
17:07:20.370 System.out: ThreadHash[144796025] Thread[DefaultDispatcher-worker-1,5,main] 我是被挂起函数withContext指定在IO线程进行的任务,挂起暂停协程A开始
17:07:25.683 System.out: ThreadHash[144796025] Thread[DefaultDispatcher-worker-1,5,main] 我是被挂起函数withContext指定在IO线程进行的任务,挂起暂停协程A结束
17:07:25.687 System.out: ThreadHash[107566656] Thread[main,5,main] 协程A--我是runBlocking启动的协程代码,我也运行在主线程
17:07:25.689 System.out: ThreadHash[107566656] Thread[main,5,main] 协程B--我是launch启动的协程代码,我运行在主线程
17:07:25.690 System.out: ThreadHash[107566656] Thread[main,5,main] 协程C--我是launch启动的协程代码,我运行在主线程

用例二执行结果只有下面一行
17:12:45.177 System.out: ThreadHash[107566656] Thread[main,5,main] 协程A--我是runBlocking启动的协程代码,我运行在主线程

结果分析
用例一中用withContext挂起函数,指定'函数中代码将要执行线程IO线程' 协程A正常被挂起,然后恢复执行,然后执行协程B协程C
用例二中用withContext挂起函数,指定'函数中代码将要执行线程仍为原线程(主线程)',主线程对函数及函数所在协程 "置之不理"
,协程会一直被挂起,不会再恢复,代码就卡这里了,多点几下应用就ANR了

由此得出结论五

V2重新梳理后的四个结论 2022-12-28

结论一. 协程是什么? 
       协程是一个线程框架,协程依附于线程或者说在线程中运行
结论二. 协程与线程关系? 
       一个线程可以启动并执行多个协程,一个协程可以依附多个线程或者说一个协程中的代码可以运行在不同线程
结论三. 协程代码执行顺序?
       同一线程的不同协程按照启动先后顺序执行代码,不同线程的不同协程并发执行(线程相同,协程不同、线程不同,协程不同)
       同一协程的不同线程代码按照顺序执行,同一协程相同线程代码按照顺序执行(线程相同,协程相同、线程不同,协程相同)
结论四. 为啥要有挂起函数? 
       一个线程执行多个协程,在不同协程间切换,依赖的是官方库定义的一些挂起函数,
       挂起函数是suspend修饰的函数,是协程的一部分,当线程执行到协程的挂起函数时
       1. 若挂起函数内部,有切换线程操作,则线程会切去执行其他协程,协程会在新线程中执行,例如挂起函数withContext,delay,原线程并未阻塞这个就是非阻塞式的含义
       2. 若挂起函数内部,无切换线程操作,则线程会继续执行当前协程,协程仍在原线程中执行,例如挂起函数coroutineScope,supervisorScope
       3. suspend关键字只是一个标志,表明函数所在协程"可能"会被挂起,诱发线程切换去执行其他协程的标志,至于会不会被挂起,参考1、2中所述

结论变更原因

发现版本一中问题1:线程跟协程的关系是怎样的?代码的执行顺序到底是怎样的?版本一中描述的有些含糊
发现版本一中问题2:‘会暂停该协程的往下执行’--协程到底暂停了没?
发现版本一中问题3:'先去执行函数体代码'--协程变成主语了?线程呢?
所以版本二中从"协程是什么","协程与线程关系","协程代码执行顺序","为啥要有挂起函数"四个角度重新归纳了四个结论

验证V2结论四

测试代码
用例一: 自定义挂起函数内调官方挂起函数进行线程切换
fun main() {
    
    
    runBlocking {
    
    
        log("协程A--开始")
        launch {
    
    
            log("协程B--开始,协程A刚切了线程,main线程开始执行我了")
            var count = 0
            repeat(Int.MAX_VALUE) {
    
    
                count++
            }
            count.also {
    
    
                log("协程B--结束")
            }
        }
        mySuspendFun()
        log("协程A--结束")
    }
}

suspend fun mySuspendFun() {
    
    
    log("协程A--挂起函数内部尚未切线程,main线程在继续执行协程A")
    withContext(Dispatchers.IO) {
    
    
        log("协程A--挂起函数内调用了一个官方的withContext挂起函数切线程" +
                 ",协程A中这块代码在新线程执行,main线程去执行其他协程了")
    }
    log("协程A--withContext函数结束返回,main线程继续执行协程A")
}

用例二: 调用没有没有线程切换到挂起函数coroutineScope与supervisorScope
fun main() {
    
    
    runBlocking {
    
     // this: CoroutineScope
        log("协程A--开始,main线程开始执行我")
        launch {
    
    
            log("协程B--开始调用delay挂起函数,在新线程做延时200ms,此时main线程可以切到其他协程")
            delay(200)
            log("协程B--delay结束返回")
        }
        coroutineScope {
    
    //挂起函数coroutineScope,创建一个协程作用域
            log("协程A--调用的coroutineScope挂起函数未切线程,main线程在继续执行我--开始")
            launch {
    
    
                log("协程C--开始调用delay挂起函数,在新线程做延时400ms,此时main线程可以切到其他协程")
                delay(400)
                log("协程C--delay结束返回,coroutineScope中子协程C已经结束,coroutineScope返回")
            }
            log("协程A--调用的coroutineScope挂起函数未切线程,main线程在继续执行我--结束,此时coroutineScope并未结束返回")
        }
        supervisorScope {
    
    //挂起函数supervisorScope,创建一个协程作用域
            log("协程A--调用supervisorScope挂起函数--开始")
            launch {
    
    
                log("协程D--开始调用delay挂起函数,在新线程做延时500ms,此时main线程可以切到其他协程")
                delay(500)
                log("协程D--delay结束返回")
            }
            log("协程A--开始调用delay挂起函数,在新线程做延时10000ms,此时main线程可以切到其他协程")
            delay(10000)
            log("协程A--delay结束返回")
            log("协程A--调用supervisorScope挂起函数--结束")
        }
        log("协程A结束")
    }
}
执行结果
用例一执行结果
ThreadHash[1262822392] Thread[main @coroutine#1,5,main] 协程A--开始
ThreadHash[1262822392] Thread[main @coroutine#1,5,main] 协程A--挂起函数内部尚未切线程,main线程在继续执行协程A
ThreadHash[1262822392] Thread[main @coroutine#2,5,main] 协程B--开始,协程A刚切了线程,main线程开始执行我了
ThreadHash[493740408] Thread[DefaultDispatcher-worker-2 @coroutine#1,5,main] 协程A--挂起函数内调用了一个官方的withContext挂起函数切线程,协程A中这块代码在新线程执行,main线程去执行其他协程了
ThreadHash[1262822392] Thread[main @coroutine#2,5,main] 协程B--结束
ThreadHash[1262822392] Thread[main @coroutine#1,5,main] 协程A--withContext函数结束返回,main线程继续执行协程A
ThreadHash[1262822392] Thread[main @coroutine#1,5,main] 协程A--结束

用例二执行结果
ThreadHash[1262822392] Thread[main @coroutine#1,5,main] 协程A--开始,main线程开始执行我
ThreadHash[1262822392] Thread[main @coroutine#1,5,main] 协程A--调用的coroutineScope挂起函数未切线程,main线程在继续执行我--开始
ThreadHash[1262822392] Thread[main @coroutine#1,5,main] 协程A--调用的coroutineScope挂起函数未切线程,main线程在继续执行我--结束,此时coroutineScope并未结束返回
ThreadHash[1262822392] Thread[main @coroutine#2,5,main] 协程B--开始调用delay挂起函数,在新线程做延时200ms,此时main线程可以切到其他协程
ThreadHash[1262822392] Thread[main @coroutine#3,5,main] 协程C--开始调用delay挂起函数,在新线程做延时400ms,此时main线程可以切到其他协程
ThreadHash[1262822392] Thread[main @coroutine#2,5,main] 协程B--delay结束返回
ThreadHash[1262822392] Thread[main @coroutine#3,5,main] 协程C--delay结束返回,coroutineScope中子协程C已经结束,coroutineScope返回
ThreadHash[1262822392] Thread[main @coroutine#1,5,main] 协程A--调用supervisorScope挂起函数--开始
ThreadHash[1262822392] Thread[main @coroutine#1,5,main] 协程A--开始调用delay挂起函数,在新线程做延时10000ms,此时main线程可以切到其他协程
ThreadHash[1262822392] Thread[main @coroutine#4,5,main] 协程D--开始调用delay挂起函数,在新线程做延时500ms,此时main线程可以切到其他协程
ThreadHash[1262822392] Thread[main @coroutine#4,5,main] 协程D--delay结束返回
ThreadHash[1262822392] Thread[main @coroutine#1,5,main] 协程A--delay结束返回
ThreadHash[1262822392] Thread[main @coroutine#1,5,main] 协程A--调用supervisorScope挂起函数--结束
ThreadHash[1262822392] Thread[main @coroutine#1,5,main] 协程A结束
结果分析
结果分析已经在日志中体现,不再重复描述

V3 版本三 2023-XX-XX

结论变更原因
说创建作用域的两个函数没有返回不一定对
测试代码
执行结果
结果分析

参考文献

kotlin官网翻译 https://www.kotlincn.net/docs/reference/coroutines/basics.html
谷歌官网 https://developer.android.google.cn/kotlin/coroutines/coroutines-best-practices
Kotlin协程在Android中的挂起流程 https://maimai.cn/article/detail?fid=1638818450&efid=Bb3L1WdcCTmFgqVubzu92w
Benny Huo  https://www.bennyhuo.com/book/kotlin-coroutines/01-intro.html#%E5%85%B3%E4%BA%8E%E4%BD%9C%E8%80%85
可能是最全的Kotlin协程讲解 https://blog.csdn.net/zou8944/article/details/106447727?share_token=b127e799-a6a7-4e2a-9073-6ce2a286e151
一文快速入门 Kotlin 协程 https://juejin.cn/post/6908271959381901325
KOTLIN中的协程,用起来原来这么简单? https://www.freesion.com/article/2493883525/
Kotlin: Suspend挂起 https://blog.csdn.net/qq_39969226/article/details/101058033
【码上开学】Kotlin 协程的挂起好神奇好难懂?今天我把它的皮给扒了  http://www.javashuo.com/article/p-uopxaafq-dd.html
Kotlin协程深入了解一波 http://192.168.120.239:4999/web/#/6/685
Kotlin-协程(4)-启动&分析执行过程 https://juejin.cn/post/6844903822834270215
Kotlin 协程一 —— 协程 Coroutine SharpCJ's blog  https://www.cnblogs.com/joy99/p/15805916.html

猜你喜欢

转载自blog.csdn.net/yfbdxz/article/details/128430674