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 之后代码变得更多了?

来看看两段代码的差异:

android kotlin 协程(五) suspend与continuation

这两段代码,只不过是回调方式不同!

那么是否能够理解为 suspendCoroutine 实质便是一个回调呢?

没错! 暂时能够理解为:suspendCoroutine 便是一个回调

再来看看 suspendCoroutine的详细完结

android kotlin 协程(五) suspend与continuation

其实质便是一个Continuation

tips: Continuation 这个角色特别重要,Continuation 是用来使挂起函数康复履行状况的

便是传说中: kotlin挂起于康复中的 康复

再来看看调用的办法:

android kotlin 协程(五) suspend与continuation

  • resume 康复正确
  • resumeWithException 康复过错

现在不理解康复不要紧, 先理解为便是一个接口回调

  • resume 回调正确
  • resumeWithException 回调过错

suspendCoroutine 的实质作用是创立一个挂起点,它会将当时协程挂起,并将协程的履行权交给调用方函数。一起,它会传入一个 Continuation 目标,该目标包含了协程的上下文和协程康复后需求履行的操作。调用方函数能够在履行完必要的操作后,调用该 Continuation 目标的 resume 办法,来唤醒协程并继续履行。

suspendCoroutine 是一个非常重要的函数,它能够让咱们将异步操作转化为同步代码风格

说的直白一点便是:

  • 不运用 suspendCoroutine 履行一个suspend的函数的时分, 康复工作由体系完结

  • 运用 suspendCoroutine会将体系的康复工作抢过来,能够经过 continuation#resume() 来自己康复

例如这样,咱们手动处理了 suspendCoroutine,但是没有康复, 就会无限挂起

android kotlin 协程(五) suspend与continuation

咱们知道在kotlin中有suspend,但是在java中并没有suspend要害字,

那么kotlin suspend函数反编译成java后是什么样的

android kotlin 协程(五) suspend与continuation

能够看出,suspend要害字并没有任何作用, 他的仅有作用便是告知开发者,我这儿需求挂起罢了

实在干活的其实是 Continuation!

现在你还觉得 suspendCoroutine 仅仅仅仅一个回调嘛?

suspendCoroutine 不仅能够操控suspend函数的康复,并且还能够让异步的代码同步化.

最要害的是线程, 线程安全不必咱们担心.

来比较一下同步代码与异步代码的风格写法:

android kotlin 协程(五) suspend与continuation

也没说异步写法欠好,黑猫白猫,捉住老鼠便是好猫,但是这仅仅一个恳求,假如说 逻辑很多,嵌套很深的话,代码会不会成这样:

android kotlin 协程(五) suspend与continuation

suspendCancellableCoroutine

suspendCoroutine 与 suspendCancellableCoroutine 的差异:

suspendCancellableCoroutine 相当所以对 suspendCoroutine 的一次封装, 增加了一些 状况,以及 能够 cancel了

  • isActive 是否活泼
  • isCancelled 是否撤销
  • isCompleted 是否履行完结

还记得这三个状况吗? Job中也有这三个状况!

suspendCancellableCoroutine 增加了 invokeOnCancellation , 该办法用来监听协程撤销, 当协程被撤销的时分会被回调

来看看下面的比如:

android kotlin 协程(五) suspend与continuation

能够看到,invokeOnCancellation 并没有履行,这儿也很好理解,因为没有cancel不履行也正常

在换一个比如

android kotlin 协程(五) suspend与continuation

这儿的要害点是await, 这是官方的一个扩展,来看看:

android kotlin 协程(五) suspend与continuation

这段代码对Cell扩展了一下, 恳求数据的时,

  • 恳求成功 就康复
  • 恳求失利 也康复,只不过会throw异常

当协程撤销的时分,将okhttp cancel掉

invokeOnCancellation 注意事项

在运用suspendCancellableCoroutine的时分,有一个办法 invokeOnCancellation

这个办法用来监听当时作用域是否撤销

先来看看运转的3种状况:

  • isActive 是否活泼
  • isCancelled 是否撤销
  • isCompleted 是否履行完结

android kotlin 协程(五) suspend与continuation

当咱们调用 Continuation#resume()康复之后, 当时协程就会被标记为完结状况

这儿有一个小细节:Continuation#cancel() 只能cancel未完结或在进行中的协程, 假如协程一旦履行完结,也便是一旦康复,那么 invokeOnCancellation则不会被调用

再来看看撤销:

android kotlin 协程(五) suspend与continuation

这种状况,应该咱们看看就会了很好理解

还有一种写法, 咱们知道,当咱们cancel父协程的时分,一切子协程也会被cancel,那么咱们就能够使用这个特性,来完结这个作用

例如这样:

android kotlin 协程(五) suspend与continuation

这儿有一个很要害的点,折磨了我好久:)

当一个挂起函数中的suspendCancellableCoroutine函数被康复(例如,经过调用continuation.resumecontinuation.resumeWithException)后,该协程就不再挂起,并且不能再被撤销。因而,在康复之后,该协程将无法响应invokeOnCancellation函数。

完整代码

下篇开端会看看协程源码, 以及手动创立协程等

下篇预告:

  • SafeContinuation
  • startCoroutine
  • createCoroutine
  • receiver startCoroutine
  • receiver createCoroutine

原创不易,您的点赞便是对我最大的支撑!