suspend挂起

一:suspend「挂起」的本质

launch ,async 创建的协程或者其他函数创建的协程,在执行到某一个 suspend 函数的时候,这个协程会被「suspend」,也就是被挂起。
那此时又是从哪里挂起?从当前线程挂起。换句话说,就是这个协程从正在执行它的线程上脱离。
注意,不是这个协程停下来了!是脱离,当前线程不再管这个协程要去做什么了。
suspend 是有暂停的意思,但我们在协程中应该理解为:当线程执行到协程的 suspend 函数的时候,暂时不继续执行协程代码了,协程脱离了线程
下面看看线程和协程各自在干什么?

线程:

挂起会让协程从正在执行它的线程上脱离.

在实际的代码逻辑中,在协程的代码块中,线程执行到了 suspend 函数这里的时候,就暂时不再执行剩余的协程代码,跳出协程的代码块。线程回去执行别的其他的任务。

如果这个线程它是 Android 的主线程,那它接下来就会继续回去工作:也就是一秒钟 60 次的界面刷新任务。

协程:

线程的代码在到达 `suspend` 函数的时候被掐断,接下来协程会从这个 `suspend` 函数开始继续往下执行,不过是在**指定的线程**。

这个指定的线程,就是 `suspend` 函数指定的,比如我们这个例子中,函数内部的 `withContext` 传入的 `Dispatchers.IO` 所指定的 IO 线程。

额外知识点:

 Dispatchers 调度器,它可以将协程限制在一个特定的线程执行,或者将它分派到一个线程池,或者让它不受限制地运行.

Dispatchers` 调度器,可以指定下面三种线程:

 Dispatchers.Main`:Android 中的主线程

Dispatchers.IO`:针对磁盘和网络 IO 进行了优化,适合 IO 密集型的任务,比如:读写文件,操作数据库以及网络请求、

 Dispatchers.Default`:适合 CPU 密集型的任务,比如计算

 )

`suspend` 函数执行完成之后,协程 会自动帮我们把线程再切回来到之前的线程。

这个「切回来」是什么意思?

 我们的协程原本是运行在**主线程**的,当代码遇到 suspend 函数的时候,发生线程切换,根据 `Dispatchers` 切换到了 IO 线程;

当这个suspend函数执行完毕后,线程又切了回来,

「切回来」也就是协程会帮我再 `post` 一个 `Runnable`,让我剩下的代码继续回到主线程去执行。
这个「切回来」的动作,在 Kotlin 里叫做 [resume](https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.coroutines.experimental/-continuation/resume.html),恢复。

二:协程的「挂起」suspend的最终解释:

协程在执行到有 suspend 标记的函数的时候,会被 suspend挂起,而所谓的被挂起,就是切个线程;

挂起函数在执行完成之后,协程会重新切回它原先的之前线程。

三:「切回来」 resume 恢复

恢复这个功能是协程的,如果你不在协程里面调用,恢复这个功能没法实现,所以,挂起函数必须在协程里,挂起函数执行完成后,才能被协程给恢复,否则无效。

四:为什么挂起函数必须在协程或者另一个挂起函数里被调用,才能切换线程 才能恢复?

一个挂起函数要么在协程里被调用,要么在另一个挂起函数里被调用,那么它其实直接或者间接地,总是会在一个协程里被调用的。

所以,要求 `suspend` 函数只能在协程里或者另一个 suspend 函数里被调用,还是为了要让协程能够在 `suspend` 函数切换线程之后再把线程给切换回来(恢复)。

五: 怎么就「挂起」了?

添加这个关键字suspend,其实并不能起到把协程挂起,或者说切换线程的作用。

真正挂起协程这件事,是 Kotlin 的协程框架帮我们做的。

所以我们想要自己写一个挂起函数,仅仅只加上 suspend 关键字是不行的,还需要函数内部直接或间接地调用到 Kotlin 协程框架自带的 suspend 函数才行,才能做到真正的挂起和切换线程的目的。

六:suspend 的作用?

  1.suspend,它并不是真正实现挂起,它其实是一个提醒。

提醒suspend函数的调用者:我是一个耗时函数,我被我的创建者用挂起的方式放在后台运行,所以请在协程里调用我。

2. 如果你创建一个 suspend 函数但它内部不包含真正的挂起逻辑,编译器会给你一个提醒:
   redundant suspend modifier,告诉你这个 suspend 是多余的。
  因为你这个函数实质上并没有发生挂起,那你这个 suspend 关键字只有一个效果:就是限制这个函数只能在协程里被调用,如果在非协程的代码中调用,就会编译不通过。
   
  3.创建一个 suspend 函数,为了让它包含真正挂起的逻辑,要在它内部直接或间接调用 Kotlin 自带的 suspend 函数,你的这个 suspend 才是有意义的。

suspend fun main() {

      suspendingPrint() // Thread: main
     suspendingGetImage() //  Thread: DefaultDispatcher-worker-1
  }

/**
 * 如果你创建一个 suspend 函数但它内部不包含真正的挂起逻辑,编译器会给你一个提醒:
 * redundant suspend modifier,告诉你这个 suspend 是多余的。
 * 因为你这个函数实质上并没有发生挂起,那你这个 suspend 关键字只有一个效果:就是限制这个函数只能在协程里被调用,如果在非协程的代码中调用,就会编译不通过。
 * */
suspend fun suspendingPrint() {
    println("Thread: ${Thread.currentThread().name}")
}

/**
 * 创建一个 suspend 函数,为了让它包含真正挂起的逻辑,要在它内部直接或间接调用 Kotlin 自带的 suspend 函数,你的这个 suspend 才是有意义的。
 * */
suspend fun suspendingGetImage() = withContext(Dispatchers.IO) {
    println("Thread: ${Thread.currentThread().name}")
}

七:什么时候需要自定义 suspend 函数?

如果你的某个函数比较耗时,也就是要等的操作,那就把它写成 suspend 函数。这就是原则。

耗时操作一般分为两类:

第一类:I/O 操作和 CPU 计算工作。比如文件的读写、网络交互、图片的模糊处理,都是耗时的,通通可以把它们写进 suspend 函数里。

第二类:就是这件事本身做起来并不慢,但它需要等待,比如 5 秒钟之后再做这个操作。这种也是 suspend 函数的应用场景。

八:怎么使用suspend关键字

给函数加上 suspend 关键字,然后在 withContext 把函数的内容包住就可以了。

提到用 withContext是因为它在挂起函数里功能最简单直接:把线程自动切走和切回。

你可能感兴趣的:(协程,kotlin)