android kotlin 协程(五) suspend与continuation
通过本篇你将学会:
-
suspendCoroutine{}
-
suspendCancellableCoroutine{}
-
suspend 与 continuation
suspendCoroutine
第一次看到这玩意的时候肯定有点身体不适, 先不用管这个东西是什么,
目前为止 只需要知道 suspendCoroutine是一个函数即可
先来想想如果不用这个suspendCoroutine ,遇到一个网络请求的原始写法是怎么样的
通常情况下,我们请求一个接口,至少需要处理2种情况
- 成功返回
- 失败返回
来看例子:
private suspend fun requestLoginNetworkData(account: String, pwd: String) =
withContext(Dispatchers.IO) {
delay(2000)// 模拟请求耗时
if (account == "123456789" && pwd == "666666") {
Result.success("登陆成功")
} else {
Result.failure(Throwable("登陆失败"))
}
}
fun main() = runBlocking<Unit> {
val deferred = async {
// 模拟网络请求
requestLoginNetworkData("987654321", "666666")
}
// 获取网络返回数据,判断成功与失败
val result = deferred.await()
// result.getOrDefault("") // 如果返回错误使用 默认值
// result.getOrThrow() // 如果返回错误使用 错误
// result.getOrNull() // 如果返回错误使用 null
result.onSuccess {
println("登陆成功:${result.getOrNull()}")
}.onFailure {
println("登陆失败:${result.getOrNull()}")
}
}
在这段代码中,我们模拟网络请求, 给一个错误的帐号密码,最终打印结果为
登陆失败:null
通过前几篇的了解,这个例子应该是非常简单的
来看看使用 suspendCoroutine怎么玩
private suspend fun <T> requestLoginNetworkData(account: String, pwd: String): String {
return withContext(Dispatchers.IO) {
delay(2000) // 模拟网络耗时需要2s
return@withContext suspendCoroutine {
if (account == "123456789" && pwd == "666666") {
it.resume("登陆成功")
} else {
it.resumeWithException(RuntimeException("登陆失败"))
}
}
}
}
suspend fun main() = runBlocking {
val scope = CoroutineScope(Dispatchers.IO)
// 开启一个协程
val deferred = scope.async {
// 模拟网络请求
requestLoginNetworkData<String>("987654321", "666666")
}
// 获取网络返回数据,判断成功与失败
val result = runCatching {
deferred.await()
}
if (result.isSuccess) {
printlnThread("登陆成功:${result.getOrNull()}")
} else {
printlnThread("登陆失败:${result.exceptionOrNull()}")
}
}
好像使用suspendCoroutine 之后代码变得更多了?
来看看两段代码的区别:
这两段代码,只不过是回调方式不同!
那么是否可以理解为 suspendCoroutine 本质就是一个回调呢?
没错! 暂时可以理解为:suspendCoroutine 就是一个回调
再来看看 suspendCoroutine的具体实现
其本质就是一个Continuation
tips: Continuation 这个角色特别重要,Continuation 是用来使挂起函数恢复执行状态的
就是传说中: kotlin挂起于恢复中的 恢复
再来看看调用的方法:
- resume 恢复正确
- resumeWithException 恢复错误
目前不理解恢复没关系, 先理解为就是一个接口回调
- resume 回调正确
- resumeWithException 回调错误
suspendCoroutine
的本质作用是创建一个挂起点,它会将当前协程挂起,并将协程的执行权交给调用方函数。同时,它会传入一个 Continuation
对象,该对象包含了协程的上下文和协程恢复后需要执行的操作。调用方函数可以在执行完必要的操作后,调用该 Continuation
对象的 resume
方法,来唤醒协程并继续执行。
suspendCoroutine 是一个非常重要的函数,它可以让我们将异步操作转化为同步代码风格
说的直白一点就是:
-
不使用 suspendCoroutine 执行一个suspend的函数的时候, 恢复工作由系统完成
-
使用 suspendCoroutine会将系统的恢复工作抢过来,可以通过 continuation#resume() 来自己恢复
例如这样,我们手动处理了 suspendCoroutine,但是没有恢复, 就会无限挂起
我们知道在kotlin中有suspend,但是在java中并没有suspend关键字,
那么kotlin suspend函数反编译成java后是什么样的
可以看出,suspend关键字并没有任何作用, 他的唯一作用就是告诉开发者,我这里需要挂起罢了
真实干活的其实是 Continuation!
现在你还觉得 suspendCoroutine 仅仅只是一个回调嘛?
suspendCoroutine 不仅可以控制suspend函数的恢复,而且还可以让异步的代码同步化.
最关键的是线程, 线程安全不用我们担心.
来比较一下同步代码与异步代码的风格写法:
也没说异步写法不好,黑猫白猫,抓住老鼠就是好猫,但是这只是一个请求,如果说 逻辑很多,嵌套很深的话,代码会不会成这样:
suspendCancellableCoroutine
suspendCoroutine 与 suspendCancellableCoroutine 的区别:
suspendCancellableCoroutine 相当于是对 suspendCoroutine 的一次封装, 增加了一些 状态,以及 可以 cancel了
- isActive 是否活跃
- isCancelled 是否取消
- isCompleted 是否执行完成
还记得这三个状态吗? Job中也有这三个状态!
suspendCancellableCoroutine 增加了 invokeOnCancellation , 该方法用来监听协程取消, 当协程被取消的时候会被回调
来看看下面的例子:
可以看到,invokeOnCancellation 并没有执行,这里也很好理解,因为没有cancel不执行也正常
在换一个例子
这里的关键点是await, 这是官方的一个扩展,来看看:
这段代码对Cell扩展了一下, 请求数据的时,
- 请求成功 就恢复
- 请求失败 也恢复,只不过会throw异常
当协程取消的时候,将okhttp cancel掉
invokeOnCancellation 注意事项
在使用suspendCancellableCoroutine的时候,有一个方法 invokeOnCancellation
这个方法用来监听当前作用域是否取消
先来看看运行的3种状态:
- isActive 是否活跃
- isCancelled 是否取消
- isCompleted 是否执行完成
当我们调用 Continuation#resume()
恢复之后, 当前协程就会被标记为完成状态
这里有一个小细节:Continuation#cancel()
只能cancel
未完成或在进行中的协程, 如果协程一旦执行完成,也就是一旦恢复,那么 invokeOnCancellation
则不会被调用
再来看看取消:
这种情况,应该大家看看就会了很好理解
还有一种写法, 我们知道,当我们cancel父协程的时候,所有子协程也会被cancel,那么我们就可以利用这个特性,来完成这个效果
例如这样:
这里有一个很关键的点,折磨了我很久:)
当一个挂起函数中的suspendCancellableCoroutine
函数被恢复(例如,通过调用continuation.resume
或continuation.resumeWithException
)后,该协程就不再挂起,并且不能再被取消。因此,在恢复之后,该协程将无法响应invokeOnCancellation
函数。
完整代码
下篇开始会看看协程源码, 以及手动创建协程等
下篇预告:
- SafeContinuation
- startCoroutine
- createCoroutine
- receiver startCoroutine
- receiver createCoroutine
原创不易,您的点赞就是对我最大的支持!