做了一次简单的实验记录一下协程执行的过程,并且写写自己的见解。
👂通俗来讲
协程是个抽象的概念,通俗而言,其实就是想让“你写的代码”,可以轻松地跨线程(指定某行在哪个线程执行,执行完又恢复到当前行来,用原来的线程继续执行),不然你写的代码如果想跨线程执行,就得用线程池或者new Thread很不美观,还容易陷入“回调地狱”。
🌟站在线程的角度看协程的API到底发生了些什么事情
线程在执行到CoroutineScope.launch时,创建了一个“协程”,这个“协程”其实可以理解为“一个跨线程的工作者”,协程是一个抽象的概念,不要想的有多高大上,其实就是Kotlin的基于线程的一套API,只不过可以实现在“某行代码”切一个线程执行,完毕后再切换回来,靠这套API实现了类似于其他语言协程的语法。
🌰举个具体的例子来说
- 众所周知,Android一直有一个主线程,运行着界面的刷新等逻辑。(主线程调用了Looper.loop,所有的代码都是在Handler执行的)
- 这个时候如果在onCreate里使用GlobalScope.launch就开启了一个“协程”,为了方便表述我们把launch{}大括号里面的代码叫“协程体”。
- launch后协程体里的代码还和线程里的代码一样,都由当前线程执行(调用launch{}本质上相当于post{协程体}的代码到主线程handler,然后handler接下来才会执行这个协程体里的代码,所以看上去协程不是立即执行的,有个短暂的延迟,非常短)。
- 但如果遇到suspend函数(比如delay,网络请求等)就直接执行完毕了这串协程体代码(协程体记录当前行号便于等会恢复),不往下执行了,本线程该干啥干啥(如果是主线程,就继续处理其他post来的任务,比如刷新ui),这样就体现出来协程不阻塞线程的作用。
- 而suspend函数里调用的withContext/async等协程api,会决定是否切线程,切到哪个线程,就由目标线程执行代码了。
- 目标工作线程运行的代码如果是网络请求,或者IO耗时操作,那那个工作线程也不能释放,而是一样在那傻等,所以协程也不能继续执行了,不是说“协程比线程厉害,IO操作时线程只能阻塞傻等,而协程可以继续做别的事情”,一个协程遇到阻塞的情况一样只能傻等。
💻具体到代码
下面代码是写在Androidapp的OnClick里的,按编号顺序看橙色【🍊橙色】注释,一定要按顺序! 注释写了每一步协程的运行具体发生了什么事情,可以看到协程是如何控制线程切换,以及suspend函数到底有什么作用:
📢至于其他帖子说的“协程是非阻塞式的”,这里解释一下:
普通线程sleep或者网络请求或者IO就会阻塞,啥都不能干,直到函数有返回值。而如果是协程,网络请求放在suspend函数里,那么协程就会记录下来调用suspend函数的位置,释放线程,让线程干其他的事情(协程体里的东西就相当于暂停执行了,也就是协程挂起),直到suspend函数执行完毕(suspend函数里的东西可能是其他线程执行),协程就会恢复到刚刚函数调用的位置,继续往下执行协程体里的内容(还是launch的那个线程)。简单来说也就是“协程偷偷切换了线程执行再切回来,看似阻塞的写法却写出了异步操作”。
📢附测试代码,可以CV到IDEA里动手试试,加深理解
// 定义一个suspend函数用于耗时操作
suspend fun getData(): String {
Log.e("YOUR_TAG", "[currentThread = ${Thread.currentThread().name}] 线程切换前")
withContext(Dispatchers.IO) {
Log.e("YOUR_TAG", "[currentThread = ${Thread.currentThread().name}] 子线程执行开始")
delay(1000L)
Log.e("YOUR_TAG", "[currentThread = ${Thread.currentThread().name}] 子线程执行结束")
}
Log.e("YOUR_TAG", "[currentThread = ${Thread.currentThread().name}] 线程切换后")
return "数据"
}
// 下面写在setOnClickListener里
Log.e("YOUR_TAG", "[currentThread = ${Thread.currentThread().name}] 主线程执行")
GlobalScope.launch(Dispatchers.Main) {
delay(1000L)
Log.e("YOUR_TAG", "[currentThread = ${Thread.currentThread().name}] 协程执行,在主线程")
val data = getData()
Log.e("YOUR_TAG", "[currentThread = ${Thread.currentThread().name}] 获取到数据,data = $data,恢复执行")
}
Log.e("YOUR_TAG", "[currentThread = ${Thread.currentThread().name}] 主线程继续执行刷新UI的操作")
有关其他协程的知识请参阅协程官方文档https://book.kotlincn.net/text/coroutines-basics.html
如果有帮助记得点个赞收藏或者点个关注,谢谢:)🙏