注册

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
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

0 个评论

要回复文章请先登录注册