Kotlin 中的suspend 关键字

Posted 码农乐园

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Kotlin 中的suspend 关键字相关的知识,希望对你有一定的参考价值。

1,初认suspend

suspend 用于暂停执行当前协程,并保存所有局部变量,被标记为 suspend 的函数只能运行在协程或者其他 suspend 函数。

首先我们看一下在retrofit 不是使用suspend关键字会造成什么错误?

IllegalArgumentException: Unable to create call adapter for 
com.qxf.sample.network.BaseResponse

没有添加suspend关键字的时候回调数据不能创建返回的数据类型,类型错误了

添加上了suspend关键字,运行时会被编译成一个 Continuation

@SinceKotlin("1.3")
public interface Continuation<in T> 
    /**
     * Context of the coroutine that corresponds to this continuation.
     */
    public val context: CoroutineContext
    /**
     * 恢复协程的调用,将成功或者失败的结果回调出去
     */
    public fun resumeWith(result: Result<T>)

也可以认为是回调,这样比较直观一些;

2,使用 suspend 函数无须关心线程切换

用这个函数不会阻塞当前调用的线程。这对 UI 编程是非常有用的,因为 UI 的主线程需要不断相应各种图形绘制、用户操作的请求,如果主线程上有耗时操作会让其他请求无法及时响应,造成 UI 卡顿

lifecycleScope.launch 
  val posts = retrofit.get<PostService>().fetchPosts();
  // 由于在主线程,可以拿着 posts 更新 UI

这相比 callback 和 RxJava 的 API 是要好很多的。这些异步的 API 最终都得依靠回调,但回调回来在哪个线程需要调用方自己搞清楚,得看这些函数里面是怎么实现的。而有了 suspend 不阻塞当前线程的约定,调用方其实无须关心这个函数内部是在哪个线程执行的。

lifecycleScope.launch(Dispatchers.Main) 
   foo()
   

如上面这个代码块,指定这个协程块调度到主线程执行,里面调用了一个不知道哪里来的 suspend foo 方法。这个方法内部可能是耗时的 CPU 计算,可能是耗时的 IO 请求,但是我在写这个协程块的时候,其实并不需要关心这里面到底是怎么回事,运行在哪个线程。类似地,在阅读这段协程块的时候,我们可以清楚地知道眼前的这段代码会在主线程执行,suspend foo 里面的代码是一个潜在的耗时操作,具体在哪个线程执行是这个函数的实现细节,对于当前代码的逻辑是「透明」的。

但前提是这个 suspend 函数实现正确,真正做到了不阻塞当前线程。单纯地给函数加上 suspend 关键字并不会神奇地让函数变成非阻塞的,比如假设 suspend foo 里面的实现是这样的:

suspend fun foo() = BigInteger.probablePrime(1024, Random())

这个 foo 函数的实现没有遵守 suspend 的语义,是错误的。正确的做法应该修改这个 foo 函数:

suspend fun findBigPrime(): BigInteger =  withContext(Dispatchers.Default)   
  BigInteger.probablePrime(4096, Random())  
  

借助 withContext 我们把耗时操作从当前主线程挪到了一个默认的后台线程池,即使是用了协程,最终还是会「阻塞」某个线程,「所有的代码本质上都是阻塞式的」,这种理解可以帮助我们认识到 android / JVM 上最终需要线程作为执行协程的载体,但忽略了阻塞和非阻塞 IO 之分,CPU 执行线程,而上面 BigInteger.probablePrime 是一个耗时的 CPU 计算,只能等待 CPU 把结果算出来,但 IO 造成的等待并不一定要阻塞 CPU,阻塞和非阻塞 IO 是有实际区别的。比如 Retrofit 虽然支持 suspend 函数(实际上也就是包装一下基于回调的 API enqueue),但是底层依赖的 OkHttp 用的是阻塞的方法,最终执行请求还是调度到线程池里面去.

把协程和 suspend 单纯看成线程切换工具有很大的局限性。由于 suspend 就是回调,也提供了包装回调 API 的方法,基于回调的 API 都可以用 suspend 函数进行封装改造.

以上是关于Kotlin 中的suspend 关键字的主要内容,如果未能解决你的问题,请参考以下文章

Kotlin基础从入门到进阶系列讲解(基础篇)关键字:suspend

Kotlin挂起函数基础

Kotlin 协程中的挂起函数是啥意思?

android kotlin 协程 suspend与continuation

kotlin协程suspend背后的逻辑和状态机思想

kotlin的suspend对比csharp的async&await