# Kotlin(一)协程入门

「这是我参与11月更文挑战的第26天,活动详情查看:2021最后一次更文挑战

一、Kotlin 协程

Kotlin 协程提供了一种全新处理并发的方式,你可以在 Android 平台上使用它来简化异步执行的代码。协程从 Kotlin 1.3 版本开始引入,但这一概念在编程世界诞生的黎明之际就有了,最早使用协程的编程语言可以追溯到 1967 年的 Simula 语言。在过去几年间,协程这个概念发展势头迅猛,现已经被诸多主流编程语言采用,比如 JavascriptC#PythonRuby 以及 Go 等。Kotlin 协程是基于来自其他语言的既定概念

Goggle 官方推荐将 Kotlin 协程作为在 Android 上进行异步编程的解决方案,值得关注的功能点包括:

  • 轻量:你可以在单个线程上运行多个协程,因为协程支持挂起,不会使正在运行协程的线程阻塞。挂起比阻塞节省内存,且支持多个并行操作
  • 内存泄露更少:使用结构化并发机制在一个作用域内执行多个操作
  • 内置取消支持取消功能会自动通过正在运行的协程层次结构传播
  • Jetpack 集成:许多 Jetpack 库都包含提供全面协程支持的扩展。某些库还提供自己的协程作用域,可供你用于结构化并发

引入依赖:

    implementation 'org.jetbrains.Kotlinx:Kotlinx-coroutines-core:1.4.2'
    implementation 'org.jetbrains.Kotlinx:Kotlinx-coroutines-android:1.4.2'
复制代码

二、第一个协程

协程可以称为轻量级线程。Kotlin 协程在 CoroutineScope 的上下文中通过 launch、async 等协程构造器(CoroutineBuilder)来声明并启动

fun main() {
    GlobalScope.launch(context = Dispatchers.IO) {
        //延时一秒
        delay(1000)
        log("launch")
    }
    //主动休眠两秒,防止JVM过快退出
    Thread.sleep(2000)
    log("end")
}
​
private fun log(msg: Any?) = println("[${Thread.currentThread().name}] $msg")
复制代码
[DefaultDispatcher-worker-1 @coroutine#1] launch
[main] end
复制代码

在上面的例子中,通过 GlobalScope(即全局作用域)启动了一个协程,在延迟一秒后输出一行日志。从输出结果可以看出来,启动的协程是运行在协程内部的线程池中。虽然从表现结果上来看,启动一个协程类似于我们直接使用 Thread 来执行耗时任务,但实际上协程和线程有着本质上的区别。通过使用协程,可以极大的提高线程的并发效率,避免以往的嵌套回调地狱,极大提高了代码的可读性

以上代码就涉及到了协程的四个基础概念:

  • suspend function。即挂起函数,delay 函数就是协程库提供的一个用于实现非阻塞式延时的挂起函数
  • CoroutineScope。即协程作用域,GlobalScope 是 CoroutineScope 的一个实现类,用于指定协程的作用范围,可用于管理多个协程的生命周期,所有协程都需要通过 CoroutineScope 来启动
  • CoroutineContext。即协程上下文,包含多种类型的配置参数。Dispatchers.IO 就是 CoroutineContext 这个抽象概念的一种实现,用于指定协程的运行载体,即用于指定协程要运行在哪类线程上
  • CoroutineBuilder。即协程构建器,协程在 CoroutineScope 的上下文中通过 launch、async 等协程构建器来进行声明并启动。launch、async 等均被声明 CoroutineScope 的扩展方法

三、suspend function

如果上述例子试图直接在 GlobalScope 外调用 delay() 函数的话,IDE 就会提示一个错误:Suspend function 'delay' should be called only from a coroutine or another suspend function。意思是:delay() 函数是一个挂起函数,只能由协程或者由其它挂起函数来调用

delay() 函数就使用了 suspend 进行修饰,用 suspend 修饰的函数就是挂起函数

public suspend fun delay(timeMillis: Long)
复制代码

读者在网上看关于协程的文章的时候,应该经常会看到这么一句话:挂起函数不会阻塞其所在线程,而是会将协程挂起,在特定的时候才再恢复协程

对于这句话我的理解是:delay() 函数类似于 Java 中的 Thread.sleep(),而之所以说 delay() 函数是非阻塞的,是因为它和单纯的线程休眠有着本质的区别。协程是运行于线程上的,一个线程可以运行多个(几千上万个)协程。线程的调度行为是由操作系统来管理的,而协程的调度行为是可以由开发者来指定并由编译器来实现的,协程能够细粒度地控制多个任务的执行时机和执行线程,当某个特定的线程上的所有协程被 suspend 后,该线程便可腾出资源去处理其他任务

例如,当在 ThreadA 上运行的 CoroutineA 调用了delay(1000L)函数指定延迟一秒后再运行,ThreadA 会转而去执行 CoroutineB,等到一秒后再来继续执行 CoroutineA。所以,ThreadA 并不会因为 CoroutineA 的延时而阻塞,而是能继续去执行其它任务,所以挂起函数并不会阻塞其所在线程,这样就极大地提高线程的并发灵活度,最大化线程的利用效率。而如果是使用Thread.sleep()的话,线程就真的只是白白消耗 CPU 时间片而不会去执行其它任务

Guess you like

Origin juejin.im/post/7035642967863853063