Kotlin协程-协程的暂停与恢复 & suspendCancellableCoroutine的使用
前言
之前在网上看到有人问协程能不能像线程一样 wait(暂停) 和 notify(恢复) 。
应用场景是开启一个线程然后执行一段逻辑,得到了某一个数据,然后需要拿到这个数据去处理一些别的事情,需要把线程先暂停,然后等逻辑处理完成之后再把线程 notify。
首先我们不说有没有其他的方式实现,我当然知道有其他多种其他实现的方式。单说这一种逻辑来看,我们使用协程能不能达到同样的效果?
那么问题来了,协程能像线程那样暂停与恢复吗?
协程默认是不能暂停与恢复的,不管是协程内部还是返回的Job对象,都不能暂停与恢复,最多只能delay延时一下,无法精准控制暂停与恢复。
但是我们可以通过 suspendCancellableCoroutine
来间接的实现这个功能。
那问题又来了,suspendCancellableCoroutine
是个什么东西?怎么用?
一、suspendCancellableCoroutine的用法
很多人不了解这个类,不知道它是干嘛的,其实我们点击去看源码就知道,源码已经给出了很清晰的注释,并且还附带了使用场景
简单的说就是把Java/Kotlin 的一些回调方法,兼容改造成 suspend 的函数,让它可以运行在协程中。
以一个非常经典的例子,网络请求我们可以通过 Retrofit+suspend 的方式,也可以直接使用 OkHttp 的方式,我们很早之前都是自己封装 OkHttpUtils 的,然后以回调的方式返回正确结果和异常处理。
我们就可以通过 suspendCancellableCoroutine 把 OkHttpUtils 的回调方式进行封装,像普通的 suspend 方法一样使用了。
fun suspendSth() {
viewModelScope.launch {
val school = mRepository.getSchool() //一个是使用Retrofit + suspend
try {
val industry = getIndustry() //一个是OkHttpUtils回调的方式
} catch (e: Exception) {
e.printStackTrace() //捕获OkHttpUtils返回的异常信息
}
}
}
private suspend fun getIndustry(): String? {
return suspendCancellableCoroutine { cancellableContinuation ->
OkhttpUtil.okHttpGet("http://www.baidu.com/api/industry", object : CallBackUtil.CallBackString() {
override fun onFailure(call: Call, e: Exception) {
cancellableContinuation.resumeWithException(e)
}
override fun onResponse(call: Call, response: String?) {
cancellableContinuation.resume(response)
}
})
}
}
感觉使用起来真是方便呢,那除了 suspendCancellableCoroutine 有没有其他的方式转换回调?有,suspendCoroutine,那它们之间的区别是什么?
suspendCancellableCoroutine 和 suspendCoroutine 区别
SuspendCancellableCoroutine 返回一个 CancellableContinuation, 它可以用 resume、resumeWithException 来处理回调和抛出 CancellationException 异常。
它与 suspendCoroutine的唯一区别就是 SuspendCancellableCoroutine 可以通过 cancel() 方法手动取消协程的执行,而 suspendCoroutine 没有该方法。
所以尽可能使用 suspendCancellableCoroutine 而不是 suspendCoroutine ,因为协程的取消是可控的。
那我们不使用回调直接用行不行?当然可以,例如:
fun suspendSth() {
viewModelScope.launch {
val school = mRepository.getSchool()
if (school is OkResult.Success) {
val lastSchool = handleSchoolData(school.data)
YYLogUtils.w("处理过后的School:" + lastSchool)
}
}
}
private suspend fun handleSchoolData(data: List<SchoolBean>?): SchoolBean? {
return suspendCancellableCoroutine {
YYLogUtils.w("通过开启一个线程延时5秒再返回")
thread {
Thread.sleep(5000)
it?.resume(mSchoolList?.last(), null)
}
}
}
那怎么能达到协程的暂停与恢复那种效果呢?我们把参数接收一下,变成成员变量不就行了吗?想什么时候resume就什么时候resume。
二、实现协程的暂停与恢复
我们定义一个方法开启一个协程,内部使用一个 suspendCancellableCoroutine 函数包裹我们的逻辑(暂停),再定义另一个方法内部使用 suspendCancellableCoroutine 的 resume 来返回给协程(恢复)。
fun suspendSth() {
viewModelScope.launch {
val school = mRepository.getSchool() //网络获取数据
if (school is OkResult.Success) {
val lastSchool = handleSchoolData(school.data)
//下面的不会执行的,除非 suspendCancellableCoroutine 的 resume 来恢复协程,才会继续走下去
YYLogUtils.w("处理过后的School:" + lastSchool)
}
}
}
private var mCancellableContinuation: CancellableContinuation<SchoolBean?>? = null
private var mSchoolList: List<SchoolBean>? = null
private suspend fun handleSchoolData(data: List<SchoolBean>?): SchoolBean? {
mSchoolList = data
return suspendCancellableCoroutine {
mCancellableContinuation = it
YYLogUtils.w("开启线程睡眠5秒再说")
thread {
Thread.sleep(5000)
YYLogUtils.w("就是不返回,哎,就是玩...")
}
}
}
//我想什么时候返回就什么时候返回
fun resumeCoroutine() {
YYLogUtils.w("点击恢复协程-返回数据")
if (mCancellableContinuation?.isCancelled == true) {
return
}
mCancellableContinuation?.resume(mSchoolList?.last(), null)
}
使用: 点击开启协程暂停了,再点击下面的按钮即恢复协程
fun testflow() {
mViewModel.suspendSth()
}
fun resumeScope() {
mViewModel.resumeCoroutine()
}
效果是点击开启协程之后我等了20秒恢复了协程,打印如下:
总结
协程虽然默认是不支持暂停与恢复,但是我们可以通过 suspendCancellableCoroutine 来间接的实现。
虽然如此,但实例开发上我还是不太推荐这么用,这样的场景我们有多种实现方式。可以用其他很好的方法实现,比如用一个协程不就好了吗串行执行,或者并发协程然后使用协程的通信来传递,或者用线程+队列也能做等等。真的一定要暂停住协程吗?不是不能实现,只是感觉不是太优雅。
(注:不好意思,这里有点主观意识了,大家不一定就要参考,毕竟它也只是一种场景需求实现的方式而已,只要性能没问题,所有的方案都是可行,大家按需选择即可)
当然关于 suspendCancellableCoroutine 谷歌的本意是让回调也能兼容协程,这也是它最大的应用场景。
本期内容如讲的不到位或错漏的地方,希望同学们可以指出交流。
如果感觉本文对你有一点点点的启发,还望你能点赞
支持一下,你的支持是我最大的动力。
Ok,这一期就此完结。
作者:newki
链接:https://juejin.cn/post/7128555351725015054
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。