一.协程的定义

  最近看了一本有关kotlin协程的书籍,对协程又有了不一样的了解,所以准备写一个关于kotlin协程系列的文章。

  言归正传,我们在学习一个新东西的时候,如果连这个东西"是什么"都回答不了,那么自然很难进入知识获取阶段的"为什么"和"怎么办"这两个后续环节了。因此,我们首先得知道协程的定义。

  协程的概念最核心的点就是一段程序能够被挂起,稍后再在挂起的位置恢复。并且,挂起和恢复是开发者的程序逻辑自己控制的,协程是通过主动挂起让出运行权来实现协作的,因此它本质上是在讨论程序控制流程的机制,这是最核心的点,任何场景下讨论协程都能落脚到挂起和恢复。

二.协程和线程的联系和区别

  联系:协程和线程都可以实现并发性,允许程序在同一时间处理多个任务;协程和线程都可以用于异步编程。

  区别:协程是一种轻量级的线程,运行在线程之上。相比线程,协程消耗的资源较少,因为每个线程都有自己的堆栈和上下文,而协程是运行在用户空间的,不需要保存上下文信息。线程在等待某种资源或者等待I/O操作完成时,会被阻塞,并且在阻塞的期间还一直霸占着CPU资源。而协程的挂起是不会阻塞线程的,运行在这个线程上的其他协程还会照常执行,并且协程挂起时会主动释放自己的CPU资源。

三.Kotlin协程的基础设施

  Kotlin的协程实现分为两个层次:

  • 基础设施层:标准库的协程API,主要对协程提供了概念和语义上最基本的支持
  • 业务框架层:协程的上层框架支持,也就是在基础设施层的基础上再封装一层

  为了便于区分,我们将Kotlin协程的基础设施层创建的协程称为简单协程,将基于业务框架层创建的协程称为复合协程,这一小节主要来讨论简单协程的使用。

  (1)简单协程的创建

val continuation=suspend{
println("协程体内")
"Hello Coroutine"
}.createCoroutine(object:Continuation<String>{
override val context: CoroutineContext
get() = EmptyCoroutineContext
override fun resumeWith(result: Result<String>) {//协程挂起后,在恢复执行时会执行该函数
println("协程运行结束,结果为:$result")
}
})

  标准库提供了一个createCoroutine函数,我们可以使用它来创建协程,但是创建的协程不会立马执行。我们先来看看它的声明:

public fun <T> (suspend () -> T).createCoroutine(
completion: Continuation<T>
): Continuation<Unit>

  其中suspend ()->T是一个被suspend修饰的挂起函数,这也是协程的执行体,不妨称作协程体

  参数completion会在协程执行完成后调用,也就是协程的完成回调

  函数的返回值是一个Continuation对象,其实也是指我们的协程体,只是套上了一层壳,协程挂起后的恢复执行,就是由它负责的

  (2)协程的启动

  调用continuation.resume(Unit)之后,协程体会立即执行。

  不过一般来说,协程创建完成之后,我们会要求它立马执行,因此标准库也提供了一个一步到位的函数:

public fun <T> (suspend () -> T).startCoroutine(
completion: Continuation<T>
) {
createCoroutineUnintercepted(completion).intercepted().resume(Unit)
}

  启动上面创建的协程,会得到返回值:

  {协程体内
  协程运行结束,结果为:Success(Hello Coroutine)}

  也就是说,协程体的返回值会作为resumeWith的参数传入,如本例中就得到Success(Hello Coroutine)

  (3)协程体的Receiver

  协程的创建和启动一共有两组api,另一组api如下:

public fun <R, T> (suspend R.() -> T).createCoroutine(
receiver: R,
completion: Continuation<T>
): Continuation<Unit> public fun <R, T> (suspend R.() -> T).startCoroutine(
receiver: R,
completion: Continuation<T>
) {
createCoroutineUnintercepted(receiver, completion).intercepted().resume(Unit)
}

  仔细观察可以发现,就是多了一个receiver参数,这个receiver参数可以为协程体提供一个作用域,在协程体内我们可以直接使用作用域内提供的函数或者状态等。

  为了方便使用带有Receiver的的协程api,我们封装一个用来启动协程的函数launch:

fun <R,T> launch(receiver:R,block:suspend R.()->T){
block.startCoroutine(receiver,object:Continuation<T>{
override val context: CoroutineContext
get() = EmptyCoroutineContext override fun resumeWith(result: Result<T>) {
println("Coroutine end:$result")
}
})
}

  使用时先创建一个作用域,可以定义一个CoroutineScope类来创建一个协程的作用域,代码如下:

class CoroutineScope{
suspend fun produce(){
println("生产一个产品")
}
} fun main() {
launch(CoroutineScope()){
produce()//直接使用作用域内提供的函数
delay(1000)
}
}

  作用域可以用来提供函数支持,自然也可以用来增加限制。如果我们为Receiver对应的类型增加一个RestrictsSuspension注解,那么在它的限制下,在协程体内就不能调用外部的挂起函数了,也就是说如果调用delay函数就会出错。

  (4)函数的挂起

  我们已经知道使用suspend关键字可以声明一个挂起函数,挂起函数只能在协程体内或其他挂起函数中调用。这样一来,整个kotlin语言体系就可以分为两派:普通函数和挂起函数。其中挂起函数中可以调用任何函数,普通函数中只能调用普通函数。

  但是,需要注意的是,挂起函数不一定真的会挂起,只是提供了挂起的条件。那什么时候才会挂起呢?在回答这个问题之前我们先来了解一个概念:挂起点,在协程内部挂起函数的调用处被称为挂起点,只有当挂起点处发生异步调用,当前协程才会被挂起,直到这个协程对应的continuation实例的resumeWith函数被调用时才会恢复执行。

  (5)协程的上下文

  协程的上下文用于提供协程启动和执行时所需要的信息,它是一个特殊的集合类型,有点像Map,集合中每个元素都是Element,并且有一个Key与之对应,Element之间可以通过"+"连接起来。主要有4种类型的Element:

  • Job:协程的唯一标识,用来管理协程的各个生命周期(new ,active,completing,completed,cancelling,cancelled)
  • CoroutineDispatcher:协程调度器,用于指定协程运行在哪个线程(IO,Main,Default,Unconfined)
  • CoroutineName:指定协程的名称
  • CoroutineExceptionHandler:指定协程的异常处理器,用来处理未捕获的异常

  协程的标准库也为我们定义了一个空的协程上下文,EmptyCoroutineContext,里面没有任何数据。

  (6)协程的拦截器

  我们现在已经知道Kotlin协程可以通过调用挂起函数实现挂起,可以通过Continuation的恢复调用实现恢复,还知道协程可以通过绑定一个上下文来设置一些数据来丰富协程的能力,那么我们最关心的问题来了,协程如何处理线程的调度?答案就是通过拦截器,它可以拦截协程异步回调时的恢复调用,那么想要操纵线程的调度应该不是什么难事。

  挂起点的恢复执行的位置可以添加拦截器来实现一些切片操作,定义拦截器只需要实现拦截器的接口,并添加到相应的协程上下文中即可。

class LogInterceptor(override val key: CoroutineContext.Key<*>) :ContinuationInterceptor{
override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> {
println("恢复调用前")
continuation.resumeWith(Result.success("恢复调用" as T))
println("恢复调用后")
return continuation
}
}

  这样一来,协程在挂起完执行恢复调用的前后就会打印出上面的日志,除了打印日志外,拦截器含有其他作用,调度器就是基于拦截器实现的,这块内容后面再提。

  

Kotlin协程系列(一)的更多相关文章

  1. Kotlin协程解析系列(上):协程调度与挂起

    vivo 互联网客户端团队- Ruan Wen 本文是Kotlin协程解析系列文章的开篇,主要介绍Kotlin协程的创建.协程调度与协程挂起相关的内容 一.协程引入 Kotlin 中引入 Corout ...

  2. Kotlin协程第一个示例剖析及Kotlin线程使用技巧

    Kotlin协程第一个示例剖析: 上一次https://www.cnblogs.com/webor2006/p/11712521.html已经对Kotlin中的协程有了理论化的了解了,这次则用代码来直 ...

  3. Retrofit使用Kotlin协程发送请求

    Retrofit2.6开始增加了对Kotlin协程的支持,可以通过suspend函数进行异步调用.本文简单介绍一下Retrofit中协程的使用 导入依赖 app的build文件中加入: impleme ...

  4. Kotlin协程基础

    开发环境 IntelliJ IDEA 2021.2.2 (Community Edition) Kotlin: 212-1.5.10-release-IJ5284.40 我们已经通过第一个例子学会了启 ...

  5. Android Kotlin协程入门

    Android官方推荐使用协程来处理异步问题.以下是协程的特点: 轻量:单个线程上可运行多个协程.协程支持挂起,不会使正在运行协程的线程阻塞.挂起比阻塞节省内存,且支持多个并行操作. 内存泄漏更少:使 ...

  6. Kotlin 协程一 —— 全面了解 Kotlin 协程

    一.协程的一些前置知识 1.1 进程和线程 1.1.1基本定义 1.1.2为什么要有线程 1.1.3 进程与线程的区别 1.2 协作式与抢占式 1.2.1 协作式 1.2.2 抢占式 1.3 协程 二 ...

  7. rxjava回调地狱-kotlin协程来帮忙

    本文探讨的是在tomcat服务端接口编程中, 异步servlet场景下( 参考我另外一个文章),用rxjava来改造接口为全流程异步方式 好处不用说 tomcat的worker线程利用率大幅提高,接口 ...

  8. Kotlin协程通信机制: Channel

    Coroutines Channels Java中的多线程通信, 总会涉及到共享状态(shared mutable state)的读写, 有同步, 死锁等问题要处理. 协程中的Channel用于协程间 ...

  9. Kotlin协程作用域与Job详解

    Job详解: 在上一次https://www.cnblogs.com/webor2006/p/11725866.html中抛出了一个问题: 所以咱们将delay去掉,需要改造一下,先把主线程的dela ...

  10. Kotlin协程作用域与构建器详解

    在上次我们是通过了这种方式来创建了一个协程: 接着再来看另一种创建协程的方式: 下面用它来实现上一次程序一样的效果,先来回顾一下上一次程序的代码: 好,下面改用runBlocking的方式: 运行一下 ...

随机推荐

  1. QTextEdit的使用

    import sys from PyQt5.QtWidgets import QApplication, QWidget, QTextEdit,QVBoxLayout, QPushButton cla ...

  2. 开源元数据管理平台Datahub最新版本0.10.5——安装部署手册(附离线安装包)

    大家好,我是独孤风. 开源元数据管理平台Datahub近期得到了飞速的发展.已经更新到了0.10.5的版本,来咨询我的小伙伴也越来越多,特别是安装过程有很多问题.本文经过和群里大伙伴的共同讨论,总结出 ...

  3. centos打开防火墙的TCP80端口

    用管理员权限运行iptables -I INPUT -p tcp --dport 80 -j ACCEPT

  4. react中使用动画 react-transition-group

    在React中通过react-transition-group使用过渡.动画,首先要有CSS3中的过渡和动画的相关知识储备,可以参考 过渡和2D变换.动画和3d变换. 我们自己通过css设置过渡.动画 ...

  5. debian11安装配置记录

    安装 软件包默认是桌面环境 + gnome + 标准安装.如果做服务器,建议标准安装 + ssh server 设置静态ip cd /etc/network/interfaces.d vi ifcfg ...

  6. VMware三种连接模式的区别

    安装了vm软件后,该软件会虚拟出两张虚拟网卡vmnet1和vmnet8 网卡在控制面板->网络和internet->更改适配器设置 三种网络连接模式: 桥接模式:使用主机的无线网卡或者有线 ...

  7. 高级SQL分析函数-窗口函数

    摘要:本文由葡萄城技术团队于博客园原创并首发.转载请注明出处:葡萄城官网,葡萄城为开发者提供专业的开发工具.解决方案和服务,赋能开发者. 前言 SQL语句中,聚合函数在统计业务数据结果时起到了重要作用 ...

  8. 数仓备份经验分享丨详解roach备份原理及问题处理套路

    本文分享自华为云社区<GaussDB(DWS) 备份问题定位思路>,作者: yd_216390446. 前言 在数据库系统中,故障分为事务内部故障.系统故障.介质(磁盘)故障.对于事务内部 ...

  9. Vue的数据更新,页面不更新的解决办法

    可能原因 更新的数据跟源数据不是同一个,即不是同一个引用 解决办法 最稳妥的办法,可通过拿到源数据取索引的方式进行数据的更新,如: 有一个源数据叫:originData 那么如果在更新时,通过this ...

  10. [Bread.Mvc] 开源一款自用 MVC 框架,支持 Native AOT

    Bread.Mvc Bread.Mvc 是一款完全支持 Native AOT 的 MVC 框架,搭配同样支持 AOT 的 Avalonia,让你的开发事半功倍.项目开源在 Gitee,欢迎 Star. ...