挂起点是什么,恢复又是什么,恢复的状态又是什么?要说明这个问题,我们需要了解 Kotlin 协程的状态机制,这个机制被隐藏在 suspend 关键字里。

我们首先看看这段耗时操作的代码:

fun main() = runBlocking {
    val nonTimeCost = measureTimeMillis { nonTimeCost() }
    println("nonTimeCost: $nonTimeCost")

    val timeCost = measureTimeMillis { println(sqrt(3, 1000)) }
    println("timeCost: $timeCost")
}

// 开根号计算,不用关心实现
fun sqrt(number: Long, precision: Int): BigDecimal {
    var x0 = BigDecimal.valueOf(1.0)
    var x1: BigDecimal
    val x2 = BigDecimal.valueOf(number)

    do {
        x1 = x0
        x0 = x1 - (x1.pow(2) - x2).divide(x1 * x2, precision, RoundingMode.HALF_UP)
    } while ((x0 - x1).abs() > BigDecimal.ONE.divide(BigDecimal.TEN.pow(precision)))

    return x0.setScale(precision, RoundingMode.HALF_UP)
}

suspend fun nonTimeCost() = delay(0)

我们的耗时操作 sqrt 函数并不需要 suspend 修饰,虽然其耗时长达 6606ms,我们也不会得到任何的编译器的警告,事实上如果加上 suspend 之后反而会得到一个编译器警告:Redundant 'suspend' modifier 。而我们的 nonTimeCost 耗费了 0 ms,但却必须加上 suspend,如果我们把 suspend 去掉,就会编译失败:“Suspend function 'delay' should be called only from a coroutine or another suspend function”

 所以 suspend 函数不一定意味着那是一个耗时的操作,而意味着这个函数里应该发生挂起,挂起的原因可能是因为有耗时的操作,也可能就是因为现在还不适合继续做后续的工作,也不能干等,所以要先去做其他的工作。同理,即使有耗时的操作也不意味着这个函数应该由 suspend 修饰,使用 suspend 的原因只是因为要在函数里执行挂起操作(调用更底层的挂起函数),在函数里直接或间接调用底层 suspend 函数是使用 suspend 修饰函数的充要条件,耗时操作则既不充分,也非必要,但确是一个非常重要的原因。常用的底层的 suspend 函数并不多,比如 delay,withContext,join,await,yield,suspendCoroutine/suspendCancellableCoroutine等等