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!

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

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

相关文章

安卓相对布局RelativeLayout

<?xml version"1.0" encoding"utf-8"?> <RelativeLayout xmlns:android"http://schemas.android.com/apk/res/android"android:layout_width"match_parent"android:layout_height"150dp"><TextViewandroid…

【微服务】Spring Boot集成ELK实用案例

推荐一款我一直在用国内很火的AI网站&#xff0c;包含GPT3.5/4.0、文心一言、通义千问、智谱AI等多个AI模型&#xff0c;支持PC、APP、VScode插件同步使用&#xff0c;点击链接跳转->ChatGPT4.0中文版 一、前言 在现代软件开发中&#xff0c;微服务架构已成为一种流行趋势。…

ChatGPT可与自定义GPTs一起使用,智能AI代理时代来啦!

1月31日凌晨&#xff0c;OpenAI在社交平台公布了一个超强新功能&#xff0c;可以在ChatGPT中输入“GPTs名字”的方法&#xff0c;调用多个自定义GPTs一起协同工作。 例如&#xff0c;我想开发一款社交APP&#xff0c;1&#xff09;可以先用专业分析GPTs做一下市场调研&#xf…

智能指针——浅析

智能指针 本人不才&#xff0c;只能将智能指针介绍一下&#xff0c;无法结合线程进行深入探索 介绍及作用 在异常产生进行跳转时&#xff0c;通过栈帧回收进行内存释放&#xff0c;防止内存泄漏 基于RAII思想可以创建出只能指针 RAII(Resource Acquisition Is Initializatio…

人麻了,刚面试入职就遇到MySQL亿级大表调优...

&#x1f4e2;&#x1f4e2;&#x1f4e2;&#x1f4e3;&#x1f4e3;&#x1f4e3; 哈喽&#xff01;大家好&#xff0c;我是【IT邦德】&#xff0c;江湖人称jeames007&#xff0c;10余年DBA及大数据工作经验 一位上进心十足的【大数据领域博主】&#xff01;&#x1f61c;&am…

基于微服务的高考志愿智能辅助决策系统(附源码)

目录 一.引言 1、编写目的 2、系统功能概述 二.功能分析 三.微服务模块 1、微服务用户相关模块 &#xff08;1&#xff09;用户注册 &#xff08;2&#xff09;用户登录 &#xff08;3&#xff09;用户信息管理 &#xff08;4&#xff09;用户操作 2、微服务文件云存…

TensorFlow2实战-系列教程13:Resnet实战1

&#x1f9e1;&#x1f49b;&#x1f49a;TensorFlow2实战-系列教程 总目录 有任何问题欢迎在下面留言 本篇文章的代码运行界面均在Jupyter Notebook中进行 本篇文章配套的代码资源已经上传 Resnet实战1 Resnet实战2 Resnet实战3 1、残差连接 深度学习中出现了随着网络的堆叠…

SpringCloud Gateway(4.1.0) 返回503:原因分析与解决方案

文章目录 一、环境版本二、原因分析三、解决方案 一、环境版本 Versionspring-cloud-dependencies2023.0.0spring-cloud-starter-gateway4.1.0Nacosv2.3.0 二、原因分析 在 Spring Cloud Gateway 的早期版本中&#xff0c;Ribbon 被用作默认的负载均衡器。随着Spring Cloud的…

jsonpath相关---JSONPath - 用于 JSON 的 XPath

一.简介 XML 的一个经常强调的优点是提供了大量工具来分析、转换和有选择地从 XML 文档中提取数据。XPath 就是这些强大的工具之一。 现在是时候想知道&#xff0c;是否需要像 XPath4JSON 这样的东西&#xff0c;以及它可以解决哪些问题。 无需特殊脚本&#xff0c;即可以交…

眼未来,萨科微半导体将持续发挥自身在技术研发和产品创新方面的优势

金航标kinghelm萨科微slkor宋仕强说&#xff0c;着眼未来,萨科微半导体将持续发挥自身在技术研发和产品创新方面的优势,以优质高效的半导体解决方案满足全球各地市场的需求。目前,萨科微的产品线已经囊括了二极管、三极管、功率器件、电源管理芯片等多个系列,并在霍尔传感器、A…

【MySQL】学习并使用聚合函数和DQL进行分组查询

&#x1f308;个人主页: Aileen_0v0 &#x1f525;热门专栏: 华为鸿蒙系统学习|计算机网络|数据结构与算法 ​&#x1f4ab;个人格言:“没有罗马,那就自己创造罗马~” #mermaid-svg-t8K8tl6eNwqdFmcD {font-family:"trebuchet ms",verdana,arial,sans-serif;font-siz…

【开源】SpringBoot框架开发天然气工程运维系统

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 系统角色分类2.2 核心功能2.2.1 流程 12.2.2 流程 22.3 各角色功能2.3.1 系统管理员功能2.3.2 用户服务部功能2.3.3 分公司&#xff08;施工单位&#xff09;功能2.3.3.1 技术员角色功能2.3.3.2 材料员角色功能 2.3.4 安…

Python算法题集_轮转数组

本文为Python算法题集之一的代码示例 题目189 题目&#xff1a;轮转数组 说明&#xff1a;给定一个整数数组 nums&#xff0c;将数组中的元素向右轮转 k 个位置&#xff0c;其中 k 是非负数。 示例 1: 输入: nums [1,2,3,4,5,6,7], k 3 输出: [5,6,7,1,2,3,4] 解释: 向右…

Hadoop3.x基础(2)- HDFS

来源&#xff1a;B站尚硅谷 目录 HDFS概述HDFS产出背景及定义HDFS优缺点HDFS组成架构HDFS文件块大小&#xff08;面试重点&#xff09; HDFS的Shell操作&#xff08;开发重点&#xff09;基本语法命令大全常用命令实操准备工作上传下载HDFS直接操作 HDFS的API操作HDFS的API案例…

微信小程序(二十八)网络请求数据进行列表渲染

注释很详细&#xff0c;直接上代码 上一篇 新增内容&#xff1a; 1.GET请求的规范 2.数据赋值的方法 源码&#xff1a; index.wxml <!-- 列表渲染基础写法&#xff0c;不明白的看上一篇 --> <view class"students"><view class"item">&…

yolov8训练自己的关键点检测模型

参考&#xff1a; https://blog.csdn.net/weixin_38807927/article/details/135036450 标注数据集 安装labelme pip install labelme -i https://pypi.tuna.tsinghua.edu.cn/simple如果报错 $ labelme 2024-01-31 03:16:20,636 [INFO ] __init__:get_config:67- Loading …

MIMIC-IV数据库, 如何提取哪些肺栓塞病人进行了溶栓手术治疗?

溶栓手术是通过药物或者手术的方式&#xff0c;使闭塞的血管再通的一种手术。 溶栓手术主要是通过药物或者手术的方式&#xff0c;使闭塞的血管再通的一种手术。常用的药物有尿激酶、链激酶等&#xff0c;这些药物可以激活纤溶酶原&#xff0c;使纤溶酶原转化为纤溶酶&#xff…

Shell的字符处理和expect

一、Here Document免交互 1.1Here Document概述 使用I/O重定向的方式将命令列表提供给交互式程序&#xff0c;标准输入的一种替代品 格式: 命令 <<标记 输入内容 标记 1.2Here Document使用注意事项 标记可以使用任意合法字符结尾的标记一定要顶格写&#xff0c;前面…

DEV-C++ ege.h库 绘图教程(九)

一、Getting Start 前情回顾&#xff1a; DEV-C ege.h库 绘图教程 今天我们将来讲一讲一些关于杂项的函数。 二、控制台函数 1.initconsole 初始化并显示控制台窗口。 &#xff08;但因为Dev C默认就是显示窗口的&#xff0c;所以这个函数一点也没用&#xff09; 但如果想…

基于C++的面向对象程序设计:类与对象的深入剖析

面向对象程序设计的基本特点 面向对象程序设计的基本特点包括&#xff1a;抽象、封装、继承、多态。 抽象 抽象是指对具体问题或对象进行概括&#xff0c;抽出其公共性质并加以描述的过程。一般情况抽象分为数据抽象和行为抽象&#xff0c;其中数据抽象是指一个对象区别于另…