Kotlin协程-协程的暂停与恢复 & suspendCancellableCoroutine的使用

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第10天,点击查看活动详情

前言

之前在网上看到有人问协程能不能像线程一样 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,这一期就此完结。

猜你喜欢

转载自juejin.im/post/7128555351725015054