[没那么难懂] Kotlin协程的通俗理解

本文通过实例解析了Kotlin协程的执行机制,展示了如何在Android中使用CoroutineScope.launch进行线程切换,以及suspend函数在控制阻塞和非阻塞操作中的作用。作者强调了协程并非总是优于线程,而是提供了一种更优雅的异步编程方式。
摘要由CSDN通过智能技术生成

做了一次简单的实验记录一下协程执行的过程,并且写写自己的见解。

👂通俗来讲

协程是个抽象的概念,通俗而言,其实就是想让“你写的代码”,可以轻松地跨线程(指定某行在哪个线程执行,执行完又恢复到当前行来,用原来的线程继续执行),不然你写的代码如果想跨线程执行,就得用线程池或者new Thread很不美观,还容易陷入“回调地狱”。

🌟站在线程的角度看协程的API到底发生了些什么事情

线程在执行到CoroutineScope.launch时,创建了一个“协程”,这个“协程”其实可以理解为“一个跨线程的工作者”,协程是一个抽象的概念,不要想的有多高大上,其实就是Kotlin的基于线程的一套API,只不过可以实现在“某行代码”切一个线程执行,完毕后再切换回来,靠这套API实现了类似于其他语言协程的语法。

🌰举个具体的例子来说

  1. 众所周知,Android一直有一个主线程,运行着界面的刷新等逻辑。(主线程调用了Looper.loop,所有的代码都是在Handler执行的)
  2. 这个时候如果在onCreate里使用GlobalScope.launch就开启了一个“协程”,为了方便表述我们把launch{}大括号里面的代码叫“协程体”。
  3. launch后协程体里的代码还和线程里的代码一样,都由当前线程执行(调用launch{}本质上相当于post{协程体}的代码到主线程handler,然后handler接下来才会执行这个协程体里的代码,所以看上去协程不是立即执行的,有个短暂的延迟,非常短)。
  4. 但如果遇到suspend函数(比如delay,网络请求等)就直接执行完毕了这串协程体代码(协程体记录当前行号便于等会恢复),不往下执行了,本线程该干啥干啥(如果是主线程,就继续处理其他post来的任务,比如刷新ui),这样就体现出来协程不阻塞线程的作用
  5. 而suspend函数里调用的withContext/async等协程api,会决定是否切线程,切到哪个线程,就由目标线程执行代码了。
  6. 目标工作线程运行的代码如果是网络请求,或者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

如果有帮助记得点个赞收藏或者点个关注,谢谢:)🙏

  • 22
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值