Kotlin新手教程九(协程)

news/2024/4/25 13:06:46/文章来源:https://blog.csdn.net/m0_49622667/article/details/129159392

一、协程

协程从Kotlin1.3开始引入,本质上协程就是轻量级的线程。协程的基本功能点有:

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

1.第一个协程

import kotlinx.coroutines.*fun main() {GlobalScope.launch { // 在后台启动一个新的协程并继续delay(1000L) // 非阻塞的等待 1 秒钟(默认时间单位是毫秒)println("World!") // 在延迟后打印输出}println("Hello,") // 协程已在等待时主线程还在继续Thread.sleep(2000L) // 阻塞主线程 2 秒钟来保证 JVM 存活
}

可以将 GlobalScope.launch { …… } 替换为 thread { …… },并将 delay(……) 替换为 Thread.sleep(……) 达到同样目的。但是如果你只更换其中一个的话就会有bug了,因为delay是一个挂起函数,不会造成线程阻塞,且只能在协程中使用。
上述代码中牵扯到以下几个概念:

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

2.桥接阻塞与非阻塞

第一个示例在同一段代码中混用了 非阻塞的 delay(……) 与 阻塞的 Thread.sleep(……)。 这容易让我们记混哪个是阻塞的、哪个是非阻塞的。 让我们显式使用 runBlocking 协程构建器来阻塞:

import kotlinx.coroutines.*fun main() {GlobalScope.launch { // 在后台启动一个新的协程并继续delay(1000L)println("World!")}println("Hello,") // 主线程中的代码会立即执行runBlocking {     // 但是这个表达式阻塞了主线程delay(2000L)  // ……我们延迟 2 秒来保证 JVM 的存活} 
}

3.协程等待(join)

与线程中的join类似,当调用join方法后会等待该协程的任务结束。

val job = GlobalScope.launch { // 启动一个新协程并保持对这个作业的引用delay(1000L)println("World!")
}
println("Hello,")
job.join() // 等待直到子协程执行结束

4.结构化的并发

当我们使用 GlobalScope.launch 时,我们会创建一个顶层协程。虽然它很轻量,但它运行时仍会消耗一些内存资源。如果我们忘记保持对新启动的协程的引用,它还会继续运行。所以我们可以通过使用runBlocking协程构造器将main函数构造为协程,这样就能在main结束时保证所有main中的协程结束。

import kotlinx.coroutines.*fun main() = runBlocking { // this: CoroutineScopelaunch { // 在 runBlocking 作用域中启动一个新协程delay(1000L)println("World!")}println("Hello,")
}

5.作用域构建器

除了由不同的构建器提供协程作用域之外,还可以使用 coroutineScope 构建器声明自己的作用域。它会创建一个协程作用域并且在所有已启动子协程执行完毕之前不会结束。

runBlocking 与 coroutineScope 可能看起来很类似,因为它们都会等待其协程体以及所有子协程结束。 主要区别在于,runBlocking 方法会阻塞当前线程来等待, 而 coroutineScope 只是挂起,会释放底层线程用于其他用途。 由于存在这点差异,runBlocking 是常规函数,而 coroutineScope 是挂起函数。

import kotlinx.coroutines.*fun main() = runBlocking { // this: CoroutineScopelaunch { delay(200L)println("Task from runBlocking")}coroutineScope { // 创建一个协程作用域launch {delay(500L) println("Task from nested launch")}delay(100L)println("Task from coroutine scope") // 这一行会在内嵌 launch 之前输出}println("Coroutine scope is over") // 这一行在内嵌 launch 执行完毕后才输出
}

6.提取函数重构

我们来将 launch { …… } 内部的代码块提取到独立的函数中。当你对这段代码执行“提取函数”重构时,你会得到一个带有 suspend 修饰符的新函数。 这是你的第一个挂起函数。在协程内部可以像普通函数一样使用挂起函数, 不过其额外特性是,同样可以使用其他挂起函数(如本例中的 delay)来挂起协程的执行。

import kotlinx.coroutines.*fun main() = runBlocking {launch { doWorld() }println("Hello,")
}// 这是你的第一个挂起函数
suspend fun doWorld() {delay(1000L)println("World!")
}

二、协程的取消与超时

1.协程的取消

在协程中可以精确控制,使用的操作就是协程的取消:

val job = launch {repeat(1000) { i ->println("job: I'm sleeping $i ...")delay(500L)}
}
delay(1300L) // 延迟一段时间
println("main: I'm tired of waiting!")
job.cancel() // 取消该作业
job.join() // 等待作业执行结束
println("main: Now I can quit.")

这里必须配合join使用的目的是,协程的取消并不是一定的,有可能取消了之后协程中还会有任务执行。我们也可以使用cancel和join的组合函数cancelAndJoin

2.协程的超时

在实践中绝大多数取消一个协程的理由是它有可能超时。 当你手动追踪一个相关 Job 的引用并启动了一个单独的协程在延迟后取消追踪,这里已经准备好使用 withTimeout 函数来做这件事。 来看看示例代码:

withTimeout(1300L) {repeat(1000) { i ->println("I'm sleeping $i ...")delay(500L)}
}

三、组合挂起函数(并发)

当我们调用两个挂起函数时,代码会根据默认顺序运行:

import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
import kotlin.system.measureTimeMillissuspend fun doSomethingUsefulOne(): Int {delay(1000L) // 假设我们在这里做了一些有用的事return 13
}suspend fun doSomethingUsefulTwo(): Int {delay(1000L) // 假设我们在这里也做了一些有用的事return 29
}
fun main()= runBlocking {val time = measureTimeMillis {val one = doSomethingUsefulOne()val two = doSomethingUsefulTwo()println("The answer is ${one + two}")}println("Completed in $time ms")
}

输出结果:
在这里插入图片描述

1.async并发

此时为了提高效率,我们可以使用async进行并发编程。async实际上启动了一个单独的协程,与launch类似,但是launch返回一个Job且无附带任何结果值,async返回一个Deferred(一个轻量级的非阻塞future),可以使用.await()在一个延期时间取得最终的值。且Deferred也是一个Job,需要时也可对其进行取消操作。

import kotlinx.coroutines.*
import kotlin.system.*fun main() = runBlocking<Unit> {val time = measureTimeMillis {val one = async { doSomethingUsefulOne() }val two = async { doSomethingUsefulTwo() }println("The answer is ${one.await() + two.await()}")}println("Completed in $time ms")    
}suspend fun doSomethingUsefulOne(): Int {delay(1000L) // 假设我们在这里做了些有用的事return 13
}suspend fun doSomethingUsefulTwo(): Int {delay(1000L) // 假设我们在这里也做了些有用的事return 29
}

在这里插入图片描述

2.惰性启动async

可选的,可以将 async 的 start 参数设置为 CoroutineStart.lazy 使其变为懒加载模式。在这种模式下,只有在主动调用 Deferred 的 await() 或者 start() 方法时才会启动协程。运行以下示例:

import kotlinx.coroutines.*
import kotlin.system.*fun main() = runBlocking<Unit> {//sampleStartval time = measureTimeMillis {val one = async(start = CoroutineStart.LAZY) { doSomethingUsefulOne() }val two = async(start = CoroutineStart.LAZY) { doSomethingUsefulTwo() }// some computationone.start() // start the first onetwo.start() // start the second oneprintln("The answer is ${one.await() + two.await()}")}println("Completed in $time ms")//sampleEnd    
}suspend fun doSomethingUsefulOne(): Int {delay(1000L) // pretend we are doing something useful herereturn 13
}suspend fun doSomethingUsefulTwo(): Int {delay(1000L) // pretend we are doing something useful here, tooreturn 29
}

以上定义了两个协程,但没有像前面的例子那样直接执行,而是将控制权交给了开发者,由开发者通过调用 start() 函数来确切地开始执行。首先启动了协程 one,然后启动了协程 two,然后再等待协程运行结束
注意,如果只是在 println 中调用了 await() 而不首先调用 start() ,这将形成顺序行为,因为 await() 会启动协程并等待其完成,这不是 lazy 模式的预期结果。async(start=CoroutineStart.LAZY) 的用例是标准标准库中的 lazy 函数的替代品,用于在值的计算涉及挂起函数的情况下。

3.使用 async 的结构化并发

让我们使用使用 async 的并发这一小节的例子并且提取出一个函数并发的调用 doSomethingUsefulOne 与 doSomethingUsefulTwo 并且返回它们两个的结果之和。 由于 async 被定义为了 CoroutineScope 上的扩展,我们需要将它写在作用域内,并且这是 coroutineScope 函数所提供的:

suspend fun concurrentSum(): Int = coroutineScope {val one = async { doSomethingUsefulOne() }val two = async { doSomethingUsefulTwo() }one.await() + two.await()
}

这种情况下,如果在 concurrentSum 函数内部发生了错误,并且它抛出了一个异常, 所有在作用域中启动的协程都会被取消。

import kotlinx.coroutines.*
import kotlin.system.*fun main() = runBlocking<Unit> {val time = measureTimeMillis {println("The answer is ${concurrentSum()}")}println("Completed in $time ms")    
}suspend fun concurrentSum(): Int = coroutineScope {val one = async { doSomethingUsefulOne() }val two = async { doSomethingUsefulTwo() }one.await() + two.await()
}suspend fun doSomethingUsefulOne(): Int {delay(1000L) // 假设我们在这里做了些有用的事return 13
}suspend fun doSomethingUsefulTwo(): Int {delay(1000L) // 假设我们在这里也做了些有用的事return 29
}

请注意,如果其中一个子协程(即 two)失败,第一个 async 以及等待中的父协程都会被取消:

import kotlinx.coroutines.*fun main() = runBlocking<Unit> {try {failedConcurrentSum()} catch(e: ArithmeticException) {println("Computation failed with ArithmeticException")}
}suspend fun failedConcurrentSum(): Int = coroutineScope {val one = async<Int> { try {delay(Long.MAX_VALUE) // 模拟一个长时间的运算42} finally {println("First child was cancelled")}}val two = async<Int> { println("Second child throws an exception")throw ArithmeticException()}one.await() + two.await()
}

上一篇:Kotlin新手教程八(泛型)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_72757.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

python -- 魔术方法

魔术方法就算定义在类里面的一些特殊的方法 特点&#xff1a;这些func的名字前面都有两个下划线 __new__方法 相当于一个类的创建一个对象的过程 __init__方法 相当于为这个类创建好的对象分配地址初始化的过程 __del__方法 一个类声明这个方法后&#xff0c;创建的对象如果…

缺少IT人员的服装行业该如何进行数字化转型?

服装行业上、下游产业链长&#xff0c;产品属性复杂&#xff0c;是劳动密集型和技术密集型紧密结合的产物&#xff0c;是典型的实体经济代表。 近二十年是服装业发展的机遇和挑战之年&#xff0c;从“世界工厂”“中国制造”&#xff0c;逐渐向“中国设计”转变,中国服装产业经…

c++常用stl算法

1、头文件 这些算法通常包含在头文件<algorithm> <functional> <numeric>中。 2、常用遍历算法 for_each(v.begin(),v.end(), 元素处理函数/仿函数) 注意&#xff1a;在使用transform转存时&#xff0c;目标容器需要提取开辟合适的空间。 void printfunc(…

数学小课堂:数学的线索(从猜想到定理再到应用的整个过程)

文章目录 引言I 勾股定理1.1 勾三股四弦五1.2 数学和自然科学的三个本质差别1.3 总结引言 从猜想到定理再到应用的整个过程是数学发展和体系构建常常经历的步骤。 I 勾股定理 勾股定理: 直角三角形两条直角边的平方之和等于斜边的平方,这个定理在国外都被称为毕达哥拉斯定理…

渗透中超全的Google hack语法

inurl:Login 将返回url中含有Login的网页intitle:后台登录管理员 将返回含有管理员后台的网页intext:后台登录 将返回含有后台的网页inurl:/admin/login.php 将返回含有admin后台的网页inurl:/phpmyadmin/index.php 将返回含有phpmyadmin后台的网页site:http://baidu.com inur:…

云计算|OpenStack|使用VMware安装华为云的R006版CNA和VRM

前言&#xff1a; FusionCompute架构 (CNA、VRM) CNA(ComputingNode Agent):计算节点代理VNA虚拟节点代理&#xff0c;部署在CNA上&#xff0c;实施计算、存储、网络的虚拟化的配置管理。VRM(Virtual Resource Manager):虚拟资源管理器 VNA可以省略不安装 本次实验使用的是V…

还在用chatGPT聊天?《元宇宙2086》已开始用AIGC做漫画连载了!

ChatGPT 是由 OpenAI开发的一个人工智能聊天机器人程序&#xff0c;于 2022 年 11 月推出。该程序使用基于 GPT-3.5架构的大型语言模型并通过强化学习进行训练。 ChatGPT 目前仍以文字方式互动&#xff0c;而除了可以透过人类自然对话方式进行交互&#xff0c;还可以用于相对复…

关于微前端,你想知道的都在这!

更多请关注微前端专题 https://codeteenager.github.io/Micro-Frontends/ 介绍 微前端官网&#xff1a;https://micro-frontends.org/ 问题&#xff1a;如何实现多个应用之间的资源共享&#xff1f; 之前比较多的处理方式是npm包形式抽离和引用&#xff0c;比如多个应用项目之…

EMR Studio Workspace 访问 Github ( 公网Git仓库 )

EMR Studio Workspace访问公网Git仓库 会遇到很多问题,由于EMR Studio不能给出任何有用的错误信息,导致排查起来非常麻烦。下面总结了若干项注意事项,可以避免踩坑。如果你遇到了同样的问题,请根据以下部分或全部建议去修正你的环境,问题即可解决。本文地址:https://laur…

因子的有效性检验(IC)

使用神经网络的预测值作为因子载荷&#xff08;因子暴露&#xff0c;因子值 factor&#xff09;时&#xff0c; 我们需要知道这个因子是否是有效的&#xff0c;所以要做因子的有效性检验。 当前的学术论文给出的IC&#xff0c; rankIC 这些都是属于判断因子是否有效的metric 因…

gdb的简单练习

题目来自《ctf安全竞赛入门》1.用vim写代码vim gdb.c#include "stdio.h" #include "stdlib.h" void main() {int i 100;int j 101;if (i j){printf("bingooooooooo.");system("/bin/sh");}elseprintf("error............&quo…

面向对象的程序设计C++课堂复盘总结 C语言复习+C++基础语法

Stay Hungry&#xff0c;Stay Foolish. 任何人都能写出机器能看懂的代码&#xff0c;但只有优秀的程序员才能写出人能看懂的代码。 有两种写程序的方式&#xff1a;一种是把代码写得非常复杂&#xff0c;以至于 “看不出明显的错误”&#xff1b;另一种是把代码写得非常简单&am…

DolphinScheduler第一章:环境安装

系列文章目录 DolphinScheduler第一章&#xff1a;环境安装 文章目录系列文章目录前言一、环境准备1.上传文件2.数据库配置3.配置安装文件二、集群部署1.数据部署2.部署 DolphinScheduler3. DolphinScheduler 启停命令总结前言 我们现在开始学习hadoop中的DolphinScheduler组…

Spring Cloud Nacos源码讲解(一)- Nacos源码分析开篇

Nacos源码开篇 Nacos服务注册与发现源码剖析 Nacos核心功能点 服务注册&#xff1a;Nacos Client会通过发送REST请求的方式向Nacos Server注册自己的服务&#xff0c;提供自身的元数据&#xff0c;比如ip地址、端口等信息。Nacos Server接收到注册请求后&#xff0c;就会把这…

Linux基础命令-uname显示系统内核信息

前言 这个命令主要是显示系统内核的相关信息&#xff0c;一般需要查看内核信息才会使用到这个命令&#xff0c;一起来看看吧。 一 命令的介绍 uname命令来自于英文词组“Unix name”的缩写&#xff0c;其功能是用于查看系统主机名、内核及硬件架构等信息。如果不加任务参数&am…

GPT-4——比GPT-3强100倍

GPT-4——比GPT-3强100倍 当前世界上最强大的人工智能系统当属ChatGPT。推出2个月用户数就突破1亿。ChatGPT是当下最炙手可热的话题&#xff0c;科技圈几乎人人都在讨论。这边ChatGPT的热度还在不断攀升&#xff0c;另一边来自《纽约时报》的最新报道称ChatGPT即将被自家超越&…

深入浅出C++ ——二叉搜索树

文章目录一、二叉搜索树概念二、二叉搜索树操作1. 二叉搜索树的查找2. 二叉搜索树的插入3. 二叉搜索树的删除三、二叉搜索树的实现四、二叉搜索树的性能分析一、二叉搜索树概念 二叉搜索树又称二叉排序树/二次查找树&#xff0c;它是一棵空树或者是每颗子树都具有以下性质的二叉…

[qiankun]-多页签缓存

[qiankun]-多页签缓存环境功能需求多页签缓存方案方案1.主服务进行html替换方案2.微服务vnode 替换方案3.每个微服务都不卸载微服务加载方式的选择微服务的路由路径选择微服务的缓存工具微服务的容器使用tab作为微服务的挂载容器使用微服务路由作为微服务的挂载容器场景描述微服…

干货解答:如何设置Facebook Messenger 自动回复?

Facebook Messenger 自动回复消息是提升客户体验的有效方法。在本文中&#xff0c;我们将探讨设置Facebook 自动响应和不同的创建方法 Facebook 自动回复。另外&#xff0c;我们准备了一些最受欢迎的 Facebook Messenger 自动回复消息。Facebook Messenger 自动回复&#xff1a…

https加密原理详解,带你搞懂它为什么比http更安全

文章目录http的缺点对称加密非对称加密数字签名数字证书验证身份数字摘要数字签名验证内容的完整性总结http的缺点 http是超文本传输协议&#xff0c;使用http协议进行通信有如下缺点&#xff1a; http没有提供任何数据加密机制&#xff0c;数据通信使用明文通信&#xff0c;…