Kotlin 中的suspend 关键字

简介: 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 函数进行封装改造.

        相关文章
        |
        11月前
        |
        API Kotlin
        Kotlin中扩展函数、infix关键字、apply函数和DSL的详解
        Kotlin中扩展函数、infix关键字、apply函数和DSL的详解
        91 0
        |
        11月前
        |
        Kotlin
        Kotlin中接口、抽象类、泛型、out(协变)、in(逆变)、reified关键字的详解
        Kotlin中接口、抽象类、泛型、out(协变)、in(逆变)、reified关键字的详解
        72 0
        |
        11月前
        |
        Kotlin
        Kotlin中继承、类型转换、Any超类、object关键字详解
        Kotlin中继承、类型转换、Any超类、object关键字详解
        105 0
        |
        11月前
        |
        Kotlin
        Kotlin 中定义类、field关键字,主构造函数和次构造函数详解
        Kotlin 中定义类、field关键字,主构造函数和次构造函数详解
        123 0
        |
        9月前
        |
        存储 Java 编译器
        Kotlin 学习笔记(四)—— 作用域函数、inline 关键字、反引号等 Kotlin 基本用法(下)
        Kotlin 学习笔记(四)—— 作用域函数、inline 关键字、反引号等 Kotlin 基本用法(下)
        39 0
        |
        9月前
        |
        Java Android开发 开发者
        Kotlin 学习笔记(四)—— 作用域函数、inline 关键字、反引号等 Kotlin 基本用法(上)
        Kotlin 学习笔记(四)—— 作用域函数、inline 关键字、反引号等 Kotlin 基本用法(上)
        69 0
        |
        Java 程序员 编译器
        01. Kotlin 标识符、关键字和注释
        01. Kotlin 标识符、关键字和注释
        92 0
        |
        设计模式 前端开发 Java
        Android体系课之--Kotlin协程进阶篇-协程的异常处理机制以及suspend关键字(三)
        协程通过将复杂性放入库来简化异步编程。程序的逻辑可以在协程中顺序地表达,而底层库会为我们解决其异步性。该库可以将用户代码的相关部分包装为回调、订阅相关事件、在不同线程(甚至不同机器!)上调度执行,而代码则保持如同顺序执行一样简单。
        |
        移动开发 安全 Java
        Kotlin学习之关键字
        Kotlin学习之关键字
        109 0
        |
        Kotlin
        【Kotlin】Kotlin 委托 ( 使用 by 关键字进行接口委托 )
        【Kotlin】Kotlin 委托 ( 使用 by 关键字进行接口委托 )
        206 0