Kotlin 协程总结

协程概念

协程就是一个线程。
协程原理:
https://juejin.cn/post/7212311942613385253#heading-15 
速通协程,一步到位!

协程就像非常轻量级的线程。线程是由系统调度的,线程切换或线程阻塞的开销都比较大。而协程依赖于线程,但是协程挂起时不需要阻塞线程,几乎是无代价的,协程是由开发者控制的。所以协程也像用户态的线程,非常轻量级,一个线程中可以创建任意个协程。

协程很重要的一点就是当它挂起的时候,它不会阻塞其他线程。协程底层库也是异步处理阻塞任务,但是这些复杂的操作被底层库封装起来,协程代码的程序流是顺序的,不再需要一堆的回调函数,就像同步代码一样,也便于理解、调试和开发。它是可控的,线程的执行和结束是由操作系统调度的,而协程可以手动控制它的执行和结束。其实就是协程自己加了回调

协程可以理解为协程各自在各自的线程上,且线程不同。其实如果多个协程共用一个线程,其实它们之间也就没有线程并发问题了

为什么需要 Kotlin 协程

提供方便的线程操作API,编写逻辑清晰且简洁的线程代码。
协程是Google在 Android 上进行异步编程的推荐解决方案。具有如下特点:

轻量:您可以在单个线程上运行多个协程,因为协程支持挂起,不会使正在运行协程的线程阻塞。挂起比阻塞节省内存,且支持多个并行操作。

内存泄漏更少:使用结构化并发机制在一个作用域内执行多项操作。
内置取消支持:取消操作会自动在运行中的整个协程层次结构内传播。
Jetpack 集成:许多 Jetpack 库都包含提供全面协程支持的扩展。某些库还提供自己的协程作用域,可供您用于结构化并发

好文章:https://blog.csdn.net/poorkick/article/details/112131961?spm=1001.2014.3001.5501

什么时候用协程

当你需要切线程或者指定线程的时候。你要在后台执行任务
好处:有回调,像同步的方式写异步代码

  1. 并发实现方便
  2. 没有回调嵌套发生, 代码结构清晰

suspend

挂起函数在执行完成之后,协程会重新切回它原先的线程
挂起,挂起函数在执行完成之后一个稍后会被自动切回来的线程调度操作。

代码执行到 suspend 函数的时候会『挂起』,并且这个『挂起』是非阻塞式的,它不会阻塞你当前的线程
「切回来」就类似于协程会帮我再 post 一个 Runnable,让我剩下的代码继续回到主线程去执行。
这个函数实质上并没有发生挂起,那你这个 suspend 关键字只有一个效果:就是限制这个函数只能在协程里被调用,如果在非协程的代码中调用,就会编译不通过。

正确用法:给函数加上 suspend 关键字,然后在 withContext 把函数的内容包住就可以了。

示例

1
2
3
4
5
6
GlobalScope.launch(Dispatchers.Main) {
  val image = suspendingGetImage(imageId)  // 获取图片
  avatarIv.setImageBitmap(image)           // 显示出来
}

suspend fun suspendingGetImage(id: String) = withContext(Dispatchers.IO) { ...}

await

await() 不会阻塞主线程,只是会挂起协程,当requestData()执行完返回结果后,processData()的逻辑会恢复执行

withContext

这个函数可以切换到指定的线程,并在闭包内的逻辑执行结束之后,自动把线程切回去继续执行
withContext 会阻塞上下文线程

二种启动

  • launch 可启动新协程而不将结果返回给调用方。任何被视为“一劳永逸”的工作都可以使用 launch 来启动。
  • async会启动一个新的协程,并允许您使用一个名为 await 的暂停函数返回结果。

串行并行

image.png|600

Job

Job 是协程的句柄。使用 launch 或 async 创建的每个协程都会返回一个 Job 实例,该实例唯一标识协程并管理其生命周期。您还可以将 Job 传递给 CoroutineScope 以进一步管理其生命周期 (关闭)

SupervisorJob

用 SupervisorJob 替代 Job,SupervisorJob 与 Job 基本类似,区别在于不会被子协程的异常所影响

处理协程异常

如何优雅的处理协程的异常?
不会影响其他,SupervisorJob 让协程自己处理异常。与之相反的,Job 会传播异常,所以 catch 代码块不会被调用

1
2
3
4
5
6
7
8
9
10
11
12
launch {
    supervisorScope {
        val task = async {
            methodThatThrowsException()
        }
        try {
            updateUI("Ok ${task.await()}")
        } catch (e: Throwable) {
            showError("Erro! ${e.message}")
        }
    }
}

image.png|800|600

Coroutine

Coroutine Scope    /ˈkəʊruːˌtiːn/ 
CoroutineScope是协程作用域,其内部本身就含有一个CoroutineContext线程,默认是主线程

而CoroutineContext则是在协程作用域中执行的线程切换。
CoroutineScope 会跟踪它使用 launch 或 async 创建的所有协程。您可以随时调用 scope.cancel() 以取消正在进行的工作(即正在运行的协程)。

viewModelScope

1
2
3
   private val viewModelScope: CoroutineScope by lazy {
        CoroutineScope(SupervisorJob() + Dispatchers.Main)
    }

Dispatchers

  • Dispatchers.Main:Android 中的主线程
  • Dispatchers.IO:针对磁盘和网络 IO 进行了优化,适合 IO 密集型的任务,比如:读写文件,操作数据库以及网络请求
  • Dispatchers.Default:适合 CPU 密集型的任务,比如计算

不阻塞怎么实现

我们要保证不阻塞,同时又要是同步式写法,该怎样做呢?异步回调callback是一种不阻塞的方式,或许协程底层也是这种实现方式(除此之外我不知道有什么不阻塞的底层实现方式),只不过外层帮我们封装成现在的同步式写法了。

普通接口回调改成协程

在 await 函数的挂起结束以后,它会调用 continuation 参数的 resumeWith 函数,来恢复执行 await 函数后面的代码。

image.png|600
image.png|600
image.png|600


Kotlin 协程总结
http://peiniwan.github.io/2024/04/3efb4556ec44.html
作者
六月的雨
发布于
2024年4月6日
许可协议