前言
协程(Coroutine),Kotlin引入的新的概念,co表示协同、协作,routine表示程序。协程义在多个互相协作的程序。
协程
协程是轻量级的线程,这可能很多人都知道,但是协程的轻量级体现在哪里呢?就是启动和切换,协程的启动不需要申请额外的退栈空间;协程的切换发生在用户态,而非内核态,避免了系统的复杂的调度,也可以说,协程就是一个封装在线程上面的线程框架。
特点
- 轻量:可以在单个线程上运行多个协程,因为协程支持挂起,不会使正在运行协程的线程阻塞。挂起比阻塞节省内存,且支持多个并行操作。
- 内存泄漏更少:使用结构化并发机制在一个作用域内执行多项操作。
- 内置取消支持:取消操作会自动在运行中的整个协程层次结构内传播。
作用
协程通过替代回调callback来简化异步代码
fun fetchDocs() {
val result = get("developer.android.com")
show(result)
}
我们都知道,Android为了保证界面的流畅和及时响应,主线程需要保持每16ms刷新一次,所以我们不能在主线程去做耗时的操作。
上图中get()通过接口去获取数据,如果主线程中去调用fetchDocs()函数就会阻塞(block)主线程,所以创建一个子线程去处理get()方法,待get()函数执行完毕后通过callback拿到结果。
fun fetchDocs(){
get("developer.android.com") { result ->
show(result)
}
}
callback 是个不错的方式,但是可能你会遇到下面这种情况,使得代码的可读性变得很差。
fun fetchDocs(){
get("developer.android.com"){
override fun success(result: String){
show(result)
}
override fun failure(msg: String){
//我希望在失败的过程中根据msg的状态重新去获取数据
get("developer.android.com") {
//......
}
}
}
}
为了解决这样的问题,协程(coroutine)就出现了。
suspend fun fetchDocs() {
val result = get("developer.android.com")
show(result)
}
suspend fun get(url: String) =
withContext(Dispatcher.IO) {
//...
}
一眼看过去,除了多了suspend修饰方法,其他流程都没有变化,为什么不会阻塞主线程呢?
被suspend修饰的函数比普通函数多了两个操作(suspend和resume)
- suspend:暂停当前协程的执行,保存所有的局部变量
- resume:从协程被暂停的地方继续执行协程
//举个例子
fun main() {
println("main before")
coroutineScope(Dispather.IO).launch {
println("before")
// get是挂起函数,会暂停当前协程的执行,保存所有局部变量。主线程会继续执行
val docs = get("developer.android.com")
// get执行完毕,resume从协程被暂停的地方继续执行协程
println("after")
}
println("main after")
}
suspend fun get(url: String) =
withContext(Dispatchers.IO) {
//...
}
suspend修饰的函数并不意味着运行在子线程中
协程的组成
取消协程,又称为协程的取消机制,这是官方的示例代码。
suspend fun main(): Unit = coroutineScope {
val job = launch {
repeat(1000) { i ->
println("job: I'm sleeping $i ...")
delay(500L)
}
}
delay(1300L) // delay a bit
println("main: I'm tired of waiting!")
job.cancel() // cancels the job
job.join() // waits for job's completion
println("main: Now I can quit.")
}
协程的延时
suspend fun main() {
delay(1000L) //delay是一个suspend函数
println("quit.")
}
指定协程线运行的线程
如果需要指定协程运行的线程,就需要指定Dispathers,常用的有三种
- Dispatchers.Main:Android中的主线程,可以直接操作UI。
- DIspatchers.IO:针对磁盘和网络IO进行了优化,适合UI密集型的任务(比如读写文件、操作数据库、网络请求)。
- DIspatchers.Default:适合CPU密集型的任务,比如解析JSON文件,排序数据量大的操作。
通过withContext()可以指定Dispatchers,这里的get()函数里的withContext代码块中指定了协程运行在Dispatchers.IO中。
launch 和 async
- launch 启动一个协程,返回一个Job,可用来取消协程;有异常直接抛出
- async 启动一个带返回结果的协程,可以通过Deferred.await()获取结果;有异常并不会直接抛出,只会在调用 await 的时候抛出