Kotlin 协程:深入理解 ‘async { }‘

Kotlin 协程:深入理解 ‘async { }’

在这里插入图片描述

Kotlin 协程是一种强大的异步编程工具,它提供了一种简洁、易读的方式来处理并发和异步操作。在 Kotlin 协程库中,async {} 是一个关键的函数,它允许我们启动一个新的协程,并返回一个 Deferred 对象,代表了一个可以被稍后获取结果的异步计算。在本篇博客中,我们将深入探讨 async {} 的工作原理,以及如何在实际的 Kotlin 代码中使用它。

协程简介

在我们深入探讨 async {} 之前,让我们先简单回顾一下协程的基本概念。协程是一种可以挂起和恢复执行的计算。与线程不同,协程的挂起和恢复不需要操作系统的介入,因此协程的开销非常小。这使得我们可以在一个程序中同时运行大量的协程,而不会像线程那样消耗大量的系统资源。

Kotlin 提供了一套丰富的协程 API,使得我们可以轻松地在 Kotlin 程序中使用协程。这些 API 包括了创建和启动协程的函数(如 launch {}async {})、挂起和恢复协程的函数(如 suspendresume)、以及处理协程异常的函数(如 CoroutineExceptionHandler)。

async {} 的基本用法

在 Kotlin 协程库中,async {} 是一个创建并启动新协程的函数。它接受一个 lambda 表达式作为参数,这个 lambda 表达式定义了协程的执行逻辑。async {} 会立即返回一个 Deferred 对象,这个对象代表了异步计算的结果。

下面是一个 async {} 的基本用法的例子:

val deferred = async {// 异步计算的逻辑computeSomething()
}
val result = deferred.await()  // 获取异步计算的结果

在这个例子中,我们首先调用 async {} 来启动一个新的协程。在协程的 lambda 表达式中,我们调用 computeSomething() 来执行异步计算。然后,我们调用 deferred.await() 来获取异步计算的结果。注意,await() 是一个挂起函数,它会挂起当前的协程,直到异步计算完成。

async {}Deferred

async {} 返回的 Deferred 对象是一个重要的概念。Deferred 是一个表示异步计算结果的接口,它继承自 Job 接口。Job 接口代表了一个可取消的计算,它有一个 cancel() 函数用于取消计算,以及一个 isCancelled 属性用于检查计算是否已被取消。

Deferred 接口添加了一个 await() 函数,这个函数会挂起当前的协程,直到异步计算完成,并返回计算的结果。如果异步计算抛出了一个异常,那么 await() 函数会重新抛出这个异常。

下面是一个 Deferred 的用法的例子:

val deferred = async {// 异步计算的逻辑computeSomething()
}
try {val result = deferred.await()  // 获取异步计算的结果
} catch (e: Exception) {// 处理异步计算的异常
}

在这个例子中,我们首先启动一个新的协程,并获取到一个 Deferred 对象。然后,我们调用 await() 函数来获取异步计算的结果,并使用 try-catch 语句来处理可能会发生的异常。

async {} 的上下文和调度器

在 Kotlin 协程中,上下文(Context)和调度器(Dispatcher)是两个重要的概念。上下文是一组键值对,它包含了一些在协程中全局有效的信息,如协程的 Job、调度器等。调度器是一个决定协程在哪个线程上执行的对象。

async {} 函数接受一个可选的上下文参数,这个参数可以用于指定新协程的上下文和调度器。如果没有指定上下文,那么新协程将继承父协程的上下文。

下面是一个 async {} 使用上下文和调度器的例子:

val deferred = async(Dispatchers.IO) {// 在 IO 调度器上执行异步计算computeSomething()
}
val result = deferred.await()  // 获取异步计算的结果

在这个例子中,我们在调用 async {} 时,传入了 Dispatchers.IO 作为上下文。这意味着新协程将在 IO 调度器上执行,这个调度器是专门用于 IO 密集型任务的。

async {} 的错误处理

async {} 也支持错误处理。如果在 async {} 的 lambda 表达式中抛出了一个异常,那么这个异常会被封装在 Deferred 对象中。当我们调用 await() 函数时,如果 Deferred 对象中有异常,那么 await() 函数会重新抛出这个异常。

下面是一个 async {} 的错误处理的例子:

val deferred = async {// 在这里抛出一个异常throw RuntimeException("Something went wrong")
}
try {val result = deferred.await()  // 这里会重新抛出异常
} catch (e: Exception) {// 在这里处理异常println(e.message)
}

在这个例子中,我们在 async {} 的 lambda 表达式中抛出了一个异常。然后,我们在调用 await() 函数时,使用 try-catch 语句来捕获和处理这个异常。

async {}launch {}

在 Kotlin 协程库中,async {}launch {} 是两个最常用的创建协程的函数。这两个函数的主要区别在于它们的返回值:async {} 返回一个 Deferred 对象,代表了一个可以被稍后获取结果的异步计算,而 launch {} 返回一个 Job 对象,代表了一个可以被取消的计算。

下面是一个 async {}launch {} 的比较的例子:

val job = launch {// 这里的代码没有返回值doSomething()
}
job.cancel()  // 可以取消这个计算val deferred = async {// 这里的代码有返回值computeSomething()
}
val result = deferred.await()  // 可以获取这个计算的结果

在这个例子中,我们首先使用 launch {} 来启动一个新的协程,然后我们可以调用 cancel() 函数来取消这个协程。然后,我们使用 async {} 来启动一个新的协程,我们可以调用 await() 函数来获取这个协程的结果。

async {} 的最佳实践

在使用 async {} 时,有一些最佳实践可以帮助我们避免常见的问题。首先,我们应该尽量减少使用 async {} 的数量。因为 async {} 会创建新的协程,如果我们在一个循环中使用 async {},那么我们可能会创建大量的协程,这可能会导致系统资源的浪费。相反,我们应该尽量使用 mapfilter 等函数来处理集合,而不是为集合中的每个元素都创建一个新的协程。

其次,我们应该避免在 async {} 中执行长时间运行的操作。因为 async {} 会立即返回,如果我们在 async {} 中执行长时间运行的操作,那么我们可能会在获取结果之前就退出了函数,这可能会导致 Deferred 对象被提前回收,从而导致异步计算的结果被丢失。

最后,我们应该避免在 async {} 中修改共享的状态。因为 async {} 会并行执行,如果我们在 async {} 中修改共享的状态,那么我们可能会遇到并发问题。为了避免这种问题,我们应该尽量使用不可变的数据结构,或者使用锁来保护共享的状态。

下面是一个 async {} 的最佳实践的例子:

val data = listOf(1, 2, 3, 4, 5)
val results = data.map { element ->async {// 对每个元素进行异步计算computeSomething(element)}
}.awaitAll()  // 获取所有异步计算的结果

在这个例子中,我们首先创建了一个数据列表。然后,我们使用 map 函数和 async {} 来对列表中的每个元素进行异步计算。最后,我们使用 awaitAll() 函数来获取所有异步计算的结果。注意,我们没有在 async {} 中修改任何共享的状态,也没有执行任何长时间运行的操作。

async {} 的局限性

尽管 async {} 是一个强大的工具,但它也有一些局限性。首先,async {} 会立即返回,这意味着我们不能在 async {} 中执行需要阻塞的操作,如读取文件或网络请求。为了处理这种情况,我们可以使用 suspend 函数,或者使用其他的并发工具,如 FuturePromise

其次,async {} 不能在没有协程的上下文中使用。也就是说,你不能在一个普通的函数中调用 async {},除非这个函数已经在一个协程中了。为了解决这个问题,我们可以使用 runBlocking {} 函数来创建一个协程的上下文。

最后,async {} 的错误处理模型可能会让人困惑。如果在 async {} 的 lambda 表达式中抛出了一个异常,那么这个异常会被封装在 Deferred 对象中,而不是立即被抛出。这意味着,如果我们忘记了调用 await() 函数,那么我们可能会错过这个异常。为了避免这种情况,我们应该总是在 await() 函数的调用处处理可能会发生的异常。

下面是一个 async {} 的局限性的例子:

val deferred = async {// 在这里抛出一个异常throw RuntimeException("Something went wrong")
}
// 在这里,我们忘记了调用 await() 函数,所以我们错过了这个异常

在这个例子中,我们在 async {} 的 lambda 表达式中抛出了一个异常。然后,我们忘记了调用 await() 函数,所以我们错过了这个异常。

async {}suspend

在 Kotlin 协程中,suspend 是一个关键的关键字,它用于声明一个可以被挂起的函数。挂起函数可以在执行过程中被暂停,并在稍后的某个时间点恢复执行。这使得我们可以在挂起函数中执行长时间运行的操作,而不会阻塞当前的线程。

async {}suspend 的一个重要区别在于它们的调用方式:async {} 必须在一个协程或者另一个挂起函数中调用,而 suspend 函数可以在任何地方调用。这使得 suspend 函数更加灵活,因为它们可以在任何需要的地方被暂停和恢复。

下面是一个 async {}suspend 的比较的例子:

suspend fun suspendFunction() {// 这里的代码可以被暂停和恢复doSomething()
}val deferred = async {// 这里的代码也可以被暂停和恢复,但它必须在一个协程或者另一个挂起函数中doSomethingElse()
}

在这个例子中,我们首先声明了一个挂起函数 suspendFunction()。在这个函数中,我们可以执行可以被暂停和恢复的代码。然后,我们使用 async {} 来启动一个新的协程,在这个协程中,我们也可以执行可以被暂停和恢复的代码。但是,与 suspendFunction() 不同,async {} 必须在一个协程或者另一个挂起函数中调用。

async {}launch {} 的选择

在 Kotlin 协程中,async {}launch {} 是两个最常用的创建协程的函数。那么,我们应该在什么情况下使用 async {},在什么情况下使用 launch {} 呢?

一般来说,如果我们需要获取协程的结果,那么我们应该使用 async {}。因为 async {} 返回一个 Deferred 对象,我们可以使用这个对象的 await() 函数来获取协程的结果。

如果我们不需要获取协程的结果,或者我们只是想启动一个并发的操作,那么我们应该使用 launch {}。因为 launch {} 返回一个 Job 对象,我们可以使用这个对象的 cancel() 函数来取消协程。

下面是一个 async {}launch {} 的选择的例子:

val deferred = async {// 我们需要获取这个计算的结果,所以我们使用 async {}computeSomething()
}
val result = deferred.await()  // 获取异步计算的结果val job = launch {// 我们不需要获取这个操作的结果,所以我们使用 launch {}doSomething()
}
job.cancel()  // 取消这个操作

在这个例子中,我们首先使用 async {} 来启动一个需要获取结果的异步计算。然后,我们使用 launch {} 来启动一个不需要获取结果的并发操作。

async {} 的性能考虑

在使用 async {} 时,我们也需要考虑性能。因为 async {} 会创建新的协程,如果我们创建了大量的协程,那么这可能会导致系统资源的浪费。为了避免这种情况,我们应该尽量减少使用 async {} 的数量,或者使用 coroutineScope {} 函数来限制协程的数量。

coroutineScope {val deferreds = List(1000) {async {// 在这里执行异步计算computeSomething(it)}}val results = deferreds.awaitAll()  // 获取所有异步计算的结果
}

在这个例子中,我们使用 coroutineScope {} 函数来创建一个协程范围。在这个范围内,我们创建了 1000 个协程。由于所有的协程都在同一个范围内,所以它们会共享相同的上下文和调度器,这可以减少系统资源的消耗。

async {} 的结构化并发

在 Kotlin 协程中,async {} 支持结构化并发。这意味着,当我们在一个协程范围内创建一个新的协程时,新的协程将成为这个范围的一个子协程。当范围被取消或者完成时,所有的子协程也会被取消或者完成。这可以确保我们的并发代码具有清晰的生命周期,并且可以避免悬挂协程的问题。

coroutineScope {val deferred = async {// 在这里执行异步计算computeSomething()}// 在这里,我们取消了协程范围,所以异步计算也会被取消cancel()
}

在这个例子中,我们首先创建了一个协程范围。然后,我们在这个范围内创建了一个新的协程。最后,我们取消了这个范围,所以新的协程也会被取消。

async {}withContext {}

在 Kotlin 协程中,async {}withContext {} 是两个常用的改变协程上下文的函数。这两个函数的主要区别在于它们的返回值:async {} 返回一个 Deferred 对象,代表了一个可以被稍后获取结果的异步计算,而 withContext {} 直接返回计算的结果。

下面是一个 async {}withContext {} 的比较的例子:

val deferred = async(Dispatchers.IO) {// 在 IO 调度器上执行异步计算computeSomething()
}
val result = deferred.await()  // 获取异步计算的结果val result2 = withContext(Dispatchers.IO) {// 在 IO 调度器上执行同步计算computeSomething()
}

在这个例子中,我们首先使用 async {} 在 IO 调度器上启动一个异步计算,然后我们使用 await() 函数来获取这个计算的结果。然后,我们使用 withContext {} 在 IO 调度器上执行一个同步计算,并直接获取这个计算的结果。

async {} 的未来

尽管 async {} 已经是一个非常强大的工具,但是 Kotlin 协程库仍然在不断发展和改进。例如,在未来的版本中,我们可能会看到更多的函数和特性被添加到 async {} 中,如更好的错误处理、更强大的调度器、更灵活的协程范围等。我们也可能会看到 async {} 在性能和效率方面得到更多的优化,使得我们可以在更大的规模和更复杂的场景中使用 async {}

结论

async {} 是 Kotlin 协程库中的一种强大的异步编程工具,它提供了一种简洁、易读的方式来处理并发和异步操作。通过深入理解 async {} 的工作原理和使用方式,我们可以更有效地使用 Kotlin 协程来构建高性能、高可读的并发代码。

无论你是一个刚开始学习 Kotlin 协程的新手,还是一个已经有一定经验的开发者,我都希望这篇博客能帮助你更好地理解和使用 async {}。如果你有任何关于 async {} 的问题或者想法,欢迎在评论区留言,我会尽力回答你的问题。

感谢阅读, Best Regards!

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

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

相关文章

【大厂AI课学习笔记】1.4 算法的进步(2)

关于感知器的兴衰。 MORE: 感知器的兴衰 一、感知器的发明与初期振动 在人工智能的历史长河中,感知器(Perceptron)无疑是一个里程碑式的存在。它最初由心理学家Frank Rosenblatt在1950年代提出,并在随后的几年中得到…

【数字电子技术课程设计】多功能数字电子钟的设计

目录 摘要 1 设计任务要求 2 设计方案及论证 2.1 任务分析 2.1.1 晶体振荡器电路 2.1.2 分频器电路 2.1.3 时间计数器电路 2.1.4 译码驱动电路 2.1.5 校时电路 2.1.6 整点报时/闹钟电路 2.2 方案比较 2.3 系统结构设计 2.4 具体电路设计 3 电路仿真测试及结…

京东物流基于 StarRocks 的数据分析平台建设

作者:京东物流 数据专家 刘敬斌 小编导读: 京东集团 2007 年开始自建物流,2017 年 4 月正式成立京东物流集团,截至目前,京东物流已经构建了一套全面的智能物流系统,实现服务自动化、运营数字化及决策智能化…

开源编辑器:ONLYOFFICE文档又更新了!

办公软件 ONLYOFFICE文档最新版本 8.0 现已发布:PDF 表单、RTL、单变量求解、图表向导、插件界面设计等更新。 什么是 ONLYOFFICE 文档 ONLYOFFICE 文档是一套功能强大的文档编辑器,支持编辑处理文本文档、电子表格、演示文稿、可填写的表单、PDF&#…

Java基础学习:System类和Static方法的实际使用

一、System类 1.在程序开发中,我们需要对这个运行的结果进行检验跟我们预判的结果是否一致,就会用到打印结果在控制台中显示出来使用到了System类。System类定义了一些和系统相关的属性和方法,它的属性和方法都是属于静态的,想使用…

数字孪生 三维建模方式以及细节步骤流程

对于数字孪生这个概念,三维建模不同行业认知都不尽相同。有的行业认为数字孪生重点在于建模,有的行业认为在于物联感知,还有部分认为是虚拟仿真。今天重点从建模角度和大家谈谈数字孪生技术常用的三维建模方式以及精细度分级。 数字孪生平台…

钉钉群机器人-发送群消息

1、钉钉群创建机器人 添加完成后,要记住 Webhook 路径; 2、机器人接入文档网址 自定义机器人接入 - 钉钉开放平台 3、JAVA代码 import com.dingtalk.api.DefaultDingTalkClient; import com.dingtalk.api.DingTalkClient; import com.dingtalk.api.re…

【靶场实战】Pikachu靶场XSS跨站脚本关卡详解

Nx01 系统介绍 Pikachu是一个带有漏洞的Web应用系统,在这里包含了常见的web安全漏洞。 如果你是一个Web渗透测试学习人员且正发愁没有合适的靶场进行练习,那么Pikachu可能正合你意。 Nx02 XSS跨站脚本概述 Cross-Site Scripting 简称为“CSS”&#xff…

聊聊ClickHouse MergeTree引擎的固定/自适应索引粒度

前言 我们在刚开始学习ClickHouse的MergeTree引擎时,就会发现建表语句的末尾总会有SETTINGS index_granularity 8192这句话(其实不写也可以),表示索引粒度为8192。在每个data part中,索引粒度参数的含义有二&#xf…

Flink 流式读取 Debezium CDC 数据写入 Hudi 表无法处理 -D / Delete 消息

问题场景是:使用 Kafka Connect 的 Debezium MySQL Source Connector 将 MySQL 的 CDC 数据 (Avro 格式)接入到 Kafka 之后,通过 Flink 读取并解析这些 CDC 数据,然后以流式方式写入到 Hudi 表中,测试中发现…

Linux mount

挂载移动硬盘 1、通过 命令 fdisk -l 查看移动硬盘 2、创建 挂载点及文件 mkdir zen 3、mount -t ntfs /dev/sdb1 zen 报错:mount: unknown filesystem type ‘ntfs’ 需要安装ntfs-3g 如下才用编译安装方法: wget https://tuxera.com/opensource/ntf…

基于Java SSM框架实现智能快递分拣系统项目【项目源码】计算机毕业设计

基于java的SSM框架实现智能快递分拣系统演示 JAVA简介 Java主要采用CORBA技术和安全模型,可以在互联网应用的数据保护。它还提供了对EJB(Enterprise JavaBeans)的全面支持,java servlet API,JSP(java serv…

解读命令docker-compose up -d

docker-compose up -d 命令是用来启动Docker Compose项目中定义的服务的,并且让这些服务在后台以守护进程(daemon)模式运行。 详细解读如下: docker-compose: 这是Docker官方提供的用于定义和管理多容器应用的工具,它…

【基础算法练习】并查集模板

文章目录 算法思想代码模板题目描述:代码并查集模板模板题二(求并查集内集合的数量) 算法思想 并查集的核心操作: 将两个集合合并询问两个元素是否在一个集合中 基本原理:每个集合我们将他维护成一颗树,…

基于Transformer结构的扩散模型综述

🎀个人主页: https://zhangxiaoshu.blog.csdn.net 📢欢迎大家:关注🔍点赞👍评论📝收藏⭐️,如有错误敬请指正! 💕未来很长,值得我们全力奔赴更美好的生活&…

npm淘宝镜像过期解决办法

npm淘宝镜像过期解决办法 因为npm 官方镜像(registry.npmjs.org)在国内访问很慢,我们基本上都会选择切换到国内的一些 npm 镜像(淘宝镜像、腾讯云镜像等)。由于淘宝原来的镜像(registry.npm.taobao.org&am…

【习题】三方库

判断题 1. 三方组件是开发者在系统能力的基础上进行了一层具体功能的封装,对其能力进行拓展的工具 正确(True) 回答正确 2. 可以通过ohpm uninstall 指令下载指定的三方库 错误(False) 回答正确 3. lottie使用loadAnimation方法加载动画。 正确(True) 回答正…

react中使用useEffcet抛出错误“超出最大更新深度”

目录 【项目中部分代码】: 【说明】: 【抛出错误】:“超出最大更新深度” 【造成原因】: 【例如:】 【解决】: 【项目中部分代码】: // 类组件中:一进页面就拿到要notiveType的…

C语言:文件操作详解

创作不易,友友们给个三连吧!! 一、为什么我们需要使用文件 我们在写程序的时候,输入的数据是存储在电脑内存中的,如果程序退出内存回收,相应数据也就丢失了,等再次运行程序,就看不到…

VR全景技术如何运用在文旅展示,VR全景技术对景区有哪些好处

引言: 随着科技的不断进步和社会的不断发展,VR全景技术越来越受到人们的关注。在文化旅游行业中,VR全景技术的应用为景区提供了全新的展示方式和体验内容,极大地丰富了游客的文化旅游体验。那么VR全景技术能给文旅展示带来哪些好…