kotlin协程之 协程概念的具像化

前言

在最开始 协程初探 的文章中,我们已经把协程的基本概念给出了定义:

kotlin 协程跟Java线程概念类似,都是用来管理并发的工具。它的底层是使用Java线程实现的,并基于线程封装了一套 API
给我们使用,让我们能够更容易的管理并发任务。

如果有人问你什么是协程,我们大概率会从概念的角度来回答,但是,概念往往都是比较抽象的,不是那么容易理解。

我们在开发中,往往是通过实际的代码来理解和使用协程的,下面,我们通过代码的角度来理解一下什么是协程。

代码中的线程

我们使用协程基本上都是为了替代掉线程,因此,往往在学习协程的时候都会拿线程来做对比。

所以在说协程之前,我们先来看看线程,在代码中,线程的具像化表现就是Thread类,我们通过Thread类来创建线程对象,然后使用线程对象来执行任务。

kotlin 复制代码
    //线程对象
    val thread = Thread {
        //执行任务
    }

    //线程的操作
    thread.start()//启动线程
    thread.interrupt()//中断线程
    thread.join()//等待线程结束
    
    //线程的状态和属性
    thread.priority//线程优先级
    thread.name//线程名字
    thread.id//线程id
    thread.isAlive//线程是否存活
    thread.isDaemon//线程是否是守护线程
    thread.isInterrupted//线程是否被中断
    thread.state//线程状态

从上述代码可以看出,线程在代码中的具像化表现是比较直观的,我们通过Thread类直接创建线程对象,然后通过线程的API来操作线程以及获取线程的属性和状态。

代码中的协程

Kotlin协程并没有给我们提供一个类似Thread的类来直接创建协程对象,而是通过 CoroutineScope

的扩展方法例如 launchasync

等方式来创建新的协程。

代码示例:

kotlin 复制代码
    //协程的创建
    GlobalScope.launch {
        //执行任务
    }
    

源码分析

launch 的源码为例:

launch 函数中会根据传入的 CoroutineStart 参数来创建一个协程对象 coroutine,然后调用 coroutine.start

来启动协程。最后把 coroutine 返回。

需要注意的是返回值的类型是 Job,而不是 LazyStandaloneCoroutine 或者 StandaloneCoroutine 类型。

这点跟线程不同,线程非常直观,返回值的类型就是 Thread,kotlin给我们提供的创建协程的api的返回值类型却是 Job

这里我们先思考一下,为什么要把返回值类型限定为 Job

接着看 launch 的源码,其中, LazyStandaloneCoroutine 是私有的,继承自 StandaloneCoroutine

StandaloneCoroutine 也是私有的,继承自 AbstractCoroutine

再来看看 AbstractCoroutine 的源码。

AbstractCoroutine 是一个抽象类,它继承或实现了以下几个接口或类:

接口/类 主要功能
Job (接口) 定义了协程的基本操作和状态查询方法。它是协程的最基本的生命周期管理接口。
JobSupport (类) JobSupport 是抽象类,实现了 Job 接口,提供了包括状态管理、取消机制、完成回调等具体的实现。
Continuation(接口) 管理协程的挂起和恢复执行,并携带协程的上下文。
CoroutineScope (接口) 协程作用域,提供了协程的创建和管理方法。

这也是为什么 launch 函数的返回值类型可以是 Job,因为 AbstractCoroutine 实现了 Job 接口。

Job

Job是一个接口,它定义了协程的基本操作和状态查询方法。其中很多api跟线程比较相近。

代码示例:

kotlin 复制代码
    //创建一个协程,其返回值是Job类型
    val job = GlobalScope.launch(start = CoroutineStart.LAZY) {
        println("Coroutine: ${Thread.currentThread().name}")
    }


    //协程的操作
    job.start()//启动协程
    job.cancel()//取消协程
    job.join()//等待协程结束
    job.invokeOnCompletion {}//协程结束时的回调
    job.cancelAndJoin()//取消并等待协程结束
    job.cancelChildren()//取消协程的子协程


    //协程的状态和属性
    job.isActive//协程是否是活动状态
    job.isCancelled//协程是否被取消
    job.isCompleted//协程是否完成
    job.parent//父协程
    job.children//子协程

上面也提到了,launch 函数的返回值类型被限定为 Job,这样,我们就只能使用 Job 接口中定义的API来操作协程了。

Job 接口主要提供的都是跟流程和状态相关的API。

这个设计细想一下也是合理的,因为我们创建协程的目的是为了执行任务,拿到协程对象后,我们只需要关心协程的执行流程和状态即可。

JobSupport

至于JobSupport,是一个抽象类,实现了Job接口,提供了包括状态管理、取消机制、完成回调等具体的实现。

Continuation

Continuation 我们比较熟悉了,在之前的文章中也有提到过。 本质上就是一个接口,内部持有了一个 CoroutineContext

类型的变量以及提供了一个抽象的 resumeWith 方法。它也是挂起和恢复的桥梁。

CoroutineScope

CoroutineScope 是一个是非常重要的接口,它提供了协程的作用域,官方定义了一些扩展方法用于创建协程,例如 launchasync 等。

CoroutineScope.launch
CoroutineScope.async

还是以 launch 的源码为例:

该函数的返回值在上面说过了,本质上就是把创建的 coroutine 限定为 Job 类型返回。

我们这里重点来关注下调用 launch 时传入的 block 参数。

block: suspend CoroutineScope.() -> Unit

  1. CoroutineScope.() 表示这是一个接收者为 CoroutineScope 类型的对象的扩展函数。也就是说在 block
    代码块中会有一个 this 指向 CoroutineScope 类型的对象,那么在代码块中可以直接访问到 CoroutineScope 的属性和方法。
  2. 该扩展被 suspend 修饰,表示它还是一个挂起函数,可以在协程中被调用。
  3. 该函数的返回值是 Unit,表示这个函数没有返回值。

也就是说,block 首先是一个函数,然后它还是 CoroutineScope 的扩展函数,最后还是一个挂起函数。

再接着看源码:

在内部创建好 coroutine 对象后,调用 coroutine.start 来启动协程。

这里注意下 start 函数中传入的 coroutineblock 参数,我们需要重点关注下类型。

如图标记所示,在 start 函数中,分别对应的形参就是 receiverblock

来看下 start 函数的源码:

重点关注 receiver: R 和 block: suspend R.() -> T 中的泛型 R

如图标记所示:

在前面我们能够得知, block 本质上就是 CoroutineScope 类型的扩展函数,因此,在这里的泛型 R 就对应 CoroutineScope 类型。

那么,这里的 receiver 的类型就也是 CoroutineScope 类型了。

前面我们分析过,coroutine 最终是作为 launch 的返回值返回的,而且在返回的时候类型被限制成了 Job

这一点也比较好理解,因为 AbstractCoroutine 既实现了 Job 接口还实现了 CoroutineScope 接口,因此,coroutine

既可以作为 Job 类型,也可以作为 CoroutineScope 类型。

下面我们来分别打印下 coroutineblock 中的 this

kotlin 复制代码
suspend fun main(args: Array<String>) {
    val job = GlobalScope.launch {
        println("block代码块中的隐式接收者this: $this")
    }
    println("job: $job")
}

结果如下:

到这里我们会发现,实际上 coroutineblock 中的隐式 CoroutineScope类型的 this 本质上就是同一个对象

,只是在不同的上下文中被调用。

总结

根据上面的分析,再来看看下面的代码示例,可以思考输出的结果。

kotlin 复制代码
suspend fun main(args: Array<String>) {

    var launchScope: CoroutineScope? = null
    //Deferred 继承自Job,本质上就是一个带有结果的 Job
    var deffered: Deferred<String>? = null

    var asyncScope: CoroutineScope? = null

    val job = GlobalScope.launch {
        launchScope = this

        withContext(Dispatchers.IO) {
            delay(1000)
            println("withContext this:${this}")
            "返回结果"
        }

        deffered = this.async {
            asyncScope = this
            "返回结果"
        }
    }

    deffered?.await()
    job.join()


    println("job: $job")
    println("launchScope:${launchScope}")
    println("job===launchScope:${job === launchScope}")

    println("deffered: $deffered")
    println("asyncScope:${asyncScope}")
    println("deffered===asyncScope:${deffered === asyncScope}")


}

运行结果:

到这里我们可以尝试从代码的角度来回答 什么是协程? 这个问题了:

协程的具像化理解

我们可以从三个视角来理解协程:

  1. 通过 CoroutineScope 的扩展方法例如 launchasync 等方式创建出来的协程,会有一个 Job
    类型的返回值,这个返回的 Job 对象就是协程。
  2. 我们在调用 launch 或者 async 方法时传递的block 代码块中隐式的 this,实际上就是 CoroutineScope 类型的对象,它是协程。
  3. block 代码块,本质上就是一个 CoroutineScope 类型的扩展函数,它也是协程。

JobCoroutineScope 都是协程的具像化表现,只是他们的能力和职责不同。

  1. Job 主要提供了跟流程和状态相关的API,例如启动、取消、等待、状态查询等,提供给开发者操作协程的能力。
  2. CoroutineScope 则范围更广,除了操作协程外,还提供了创建协程的方法,例如 launchasync
    等,我们在开发中主要也都是使用 CoroutineScope 创建协程的能力。

这样做也是为了把职责分离,让开发者能够更自然地使用协程的API。

本篇文章算是在初步会使用协程后对概念的加深,只有概念理解清楚了,后面再去理解更高级的特性才能事半功倍。

好了,本篇文章就是这样,希望能帮到你。


感谢阅读,如果对你有帮助请点赞支持。有任何疑问或建议,欢迎在评论区留言讨论。如需转载,请注明出处:喻志强的博客

相关推荐
晨曦_子画33 分钟前
编程语言之战:AI 之后的 Kotlin 与 Java
android·java·开发语言·人工智能·kotlin
大福是小强34 分钟前
005-Kotlin界面开发之程序猿初试Composable
kotlin·界面开发·桌面应用·compose·jetpack·可组合
xiaoxiao涛1 小时前
协程6 --- HOOK
c++·协程
&岁月不待人&3 小时前
Kotlin by lazy和lateinit的使用及区别
android·开发语言·kotlin
小白学大数据6 小时前
正则表达式在Kotlin中的应用:提取图片链接
开发语言·python·selenium·正则表达式·kotlin
bytebeats2 天前
Kotlin 注解全面指北
android·java·kotlin
jzlhll1232 天前
kotlin android Handler removeCallbacks runnable不生效的一种可能
android·开发语言·kotlin
&岁月不待人&2 天前
Kotlin 协程使用及其详解
开发语言·kotlin
苏柘_level62 天前
【Kotlin】 基础语法笔记
开发语言·笔记·kotlin
大福是小强2 天前
002-Kotlin界面开发之Kotlin旋风之旅
kotlin·函数式编程·lambda·语法·运算符重载·扩展函数