Kotlin协程系列(一)
一.协程的定义
最近看了一本有关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协程系列(一)的更多相关文章
- Kotlin协程解析系列(上):协程调度与挂起
vivo 互联网客户端团队- Ruan Wen 本文是Kotlin协程解析系列文章的开篇,主要介绍Kotlin协程的创建.协程调度与协程挂起相关的内容 一.协程引入 Kotlin 中引入 Corout ...
- Kotlin协程第一个示例剖析及Kotlin线程使用技巧
Kotlin协程第一个示例剖析: 上一次https://www.cnblogs.com/webor2006/p/11712521.html已经对Kotlin中的协程有了理论化的了解了,这次则用代码来直 ...
- Retrofit使用Kotlin协程发送请求
Retrofit2.6开始增加了对Kotlin协程的支持,可以通过suspend函数进行异步调用.本文简单介绍一下Retrofit中协程的使用 导入依赖 app的build文件中加入: impleme ...
- Kotlin协程基础
开发环境 IntelliJ IDEA 2021.2.2 (Community Edition) Kotlin: 212-1.5.10-release-IJ5284.40 我们已经通过第一个例子学会了启 ...
- Android Kotlin协程入门
Android官方推荐使用协程来处理异步问题.以下是协程的特点: 轻量:单个线程上可运行多个协程.协程支持挂起,不会使正在运行协程的线程阻塞.挂起比阻塞节省内存,且支持多个并行操作. 内存泄漏更少:使 ...
- Kotlin 协程一 —— 全面了解 Kotlin 协程
一.协程的一些前置知识 1.1 进程和线程 1.1.1基本定义 1.1.2为什么要有线程 1.1.3 进程与线程的区别 1.2 协作式与抢占式 1.2.1 协作式 1.2.2 抢占式 1.3 协程 二 ...
- rxjava回调地狱-kotlin协程来帮忙
本文探讨的是在tomcat服务端接口编程中, 异步servlet场景下( 参考我另外一个文章),用rxjava来改造接口为全流程异步方式 好处不用说 tomcat的worker线程利用率大幅提高,接口 ...
- Kotlin协程通信机制: Channel
Coroutines Channels Java中的多线程通信, 总会涉及到共享状态(shared mutable state)的读写, 有同步, 死锁等问题要处理. 协程中的Channel用于协程间 ...
- Kotlin协程作用域与Job详解
Job详解: 在上一次https://www.cnblogs.com/webor2006/p/11725866.html中抛出了一个问题: 所以咱们将delay去掉,需要改造一下,先把主线程的dela ...
- Kotlin协程作用域与构建器详解
在上次我们是通过了这种方式来创建了一个协程: 接着再来看另一种创建协程的方式: 下面用它来实现上一次程序一样的效果,先来回顾一下上一次程序的代码: 好,下面改用runBlocking的方式: 运行一下 ...
随机推荐
- 记一次使用pagehelper的坑(返回的total和size每页条数一致的问题)
问题描述 众所周知,pagehelper使用时应该在dao查询语句的前一句加上PageHelper.startPage,所以标题的问题由此引出-- 原因 PageHelper.startPage使用后 ...
- 基于AIidlux平台的自动驾驶环境感知与智能预警
自动驾驶汽车又称为无人驾驶车,是一种需要驾驶员辅助或者完全不需操控的车辆. 自动驾驶分级: 自动驾驶系统的组成部分: 环境感知系统: 自动驾驶系统架构: 自动驾驶数据集: Aidlux的作用: YOL ...
- 【教程】AWD中如何通过Python批量快速管理服务器?
前言 很多同学都知道,我们常见的CTF赛事除了解题赛之外,还有一种赛制叫AWD赛制.在这种赛制下,我们战队会拿到一个或多个服务器.服务器的连接方式通常是SSH链接,并且可能一个战队可能会同时有多个服务 ...
- 来会会babel这个重要且神奇的工具
babel 在前端工程化开发中发挥着至关重要的作用,它能将较高级的语法转成浏览器可识别的代码,无论中 es6 中 const .promise 还是 React.TypeScript. 以下babel ...
- 【技术积累】Linux中的命令行【理论篇】【七】
atrm命令 命令介绍 atrm命令是Linux系统中的一个命令行工具,用于取消或删除已经安排的at命令.at命令是一种用于在指定时间执行一次性任务的工具. 命令说明 atrm命令的语法如下: atr ...
- LeetCode 周赛上分之旅 #40 结合特征压缩的数位 DP 问题
️ 本文已收录到 AndroidFamily,技术和职场问题,请关注公众号 [彭旭锐] 和 BaguTree Pro 知识星球提问. 学习数据结构与算法的关键在于掌握问题背后的算法思维框架,你的思考越 ...
- [ABC150F] Xor Shift
2023-03-10 题目 题目传送门 翻译 翻译 难度&重要性(1~10):6 题目来源 AtCoder 题目算法 KMP,Z函数 解题思路 首先是按位确定,令 \(t(i,j)\) 表示 ...
- 基于 LLM 的知识图谱另类实践
本文整理自社区用户陈卓见在「夜谈 LLM」主题分享上的演讲,主要包括以下内容: 利用大模型构建知识图谱 利用大模型操作结构化数据 利用大模型使用工具 利用大模型构建知识图谱 上图是之前,我基于大语言模 ...
- Teamcenter RAC 开发之《Excel模版导出》
背景 在做 Teamcenter RAC客制化表单后,TMD肯定有一个需求要导出表单,毕竟所谓的客制化表单就是从纸质表单中出来的,那么写代码必不可少......... 那么问题来了,对于一个Excel ...
- Go 项目代码布局
Go 项目代码布局 目录 Go 项目代码布局 一.Go 语言"创世项目"结构 1.1 src 目录结构三个特点 二.Go 项目布局演进 2.1 演进一:Go 1.4 版本删除 pk ...