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的方式: 运行一下 ...
随机推荐
- 26194136 psu安装步骤
26194136 psu安装步骤 1.拷贝 安装包p26194136_112040_MSWIN-x86-64.zip到 目录 2..关闭rac crsctl stop crs srvctl stop ...
- 仅三天,我用 GPT-4 生成了性能全网第一的 Golang Worker Pool,轻松打败 GitHub 万星项目
目录 1. 我写了一个超牛的开源项目 1.1 你看看这性能 1.2 你看看这功能 1.3 你猜我这一百天都经历了啥 2. 你有多久没写并发程序了? 3. 问:一个 Worker Pool 程序需要包含 ...
- 解读 --- Span<T>
引言 Span<T> 是C# 中的一种结构体,它是一种内存安全的类型,可以用来表示连续的内存区域.Span<T> 可以被用于访问和操作数组.堆上分配的内存和栈上分配的内存.使用 ...
- 文心一言 VS 讯飞星火 VS chatgpt (79)-- 算法导论7.4 4题
四.请用go语言,证明:RANDOMIZED-QUICKSORT 期望运行时间是 Ω(nlgn). 文心一言: 首先,为了证明RANDOMIZED-QUICKSORT的期望运行时间是Ω(nlg n), ...
- Jmeter读取结果文件报错Error loading results file解决方法
最近在项目性能测试过程中,遇到jmeter读取jtl文件出错的问题,如下图所示: 方法一:修改配置文件 将要读取结果文件的组件Configure界面配置都勾选上,默认情况下有些选项没勾选会出错. 第一 ...
- 《小白WEB安全入门》02. 开发篇
@ 目录 初识HTML潜在漏洞 初识CSS潜在漏洞 初识JS潜在漏洞 初识后端潜在漏洞 后端能做什么 后端种类 后端框架 潜在漏洞 本系列文章只叙述一些超级基础理论知识,极少有实践部分 本文涉及到的语 ...
- ZooKeeper核心概念和架构
ZooKeeper简介 ZooKeeper是一个分布式应用程序协调服务,主要用于解决分布式集群中应用系统的一致性问题.它能提供类似文件系统的目录节点树方式的数据存储,主要用途是维护和监控所存数据的状态 ...
- 文心一言 VS 讯飞星火 VS chatgpt (90)-- 算法导论8.3 3题
三.用go语言,利用归纳法来证明基数排序是正确的.在你所给出的证明中,在哪里需要假设所用的底层排序算法是稳定的? 文心一言: 基数排序是一种非比较型整数排序算法,其通过在每一位上进行比较来排序.基数排 ...
- 机器学习(6)K近邻算法
k-近邻,通过离你最近的来判断你的类别 例子: 定义:如果一个样本在特征空间中的k个最相似(即特征空间中最邻近的样本中大多数属于某一类别),则该样本属于这个类别 K近邻需要做标准化处理 例如: imp ...
- 解密网络通信的关键技术(上):DNS、ARP、DHCP和NAT,你了解多少?
IP 协议相关技术 在与 IP 协议相关的技术中,有一些重要且常见的技术,其中包括 DNS 域名解析.ARP 协议.DHCP 动态获取 IP 地址以及NAT 网络地址转换.这些技术在网络通信中起着关键 ...