Kotlin:组合挂起函数

点击查看:组合挂起函数 中文官网

点击查看:组合挂起函数 英文文档

默认顺序调用

假设我们在不同的地方定义了两个进行某种调用远程服务或者进行计算的挂起函数。我们只假设它们都是有用的,但是实际上它们在这个示例中只是为了该目的而延迟了一秒钟:

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

如果需要按 顺序 调用它们,我们接下来会做什么——首先调用 doSomethingUsefulOne 接下来 调用 doSomethingUsefulTwo,并且计算它们结果的和吗? 实际上,如果我们要根据第一个函数的结果来决定是否我们需要调用第二个函数或者决定如何调用它时,我们就会这样做。

我们使用普通的顺序来进行调用,因为这些代码是运行在协程中的,只要像常规的代码一样 顺序 都是默认的。下面的示例展示了测量执行两个挂起函数所需要的总时间:

运行结果
在这里插入图片描述

使用 async 并发

如果 doSomethingUsefulOne 与 doSomethingUsefulTwo 之间没有依赖,并且我们想更快的得到结果,让它们进行 并发 吗?这就是 async 可以帮助我们的地方。

在概念上,async 就类似于 launch。它启动了一个单独的协程,这是一个轻量级的线程并与其它所有的协程一起并发的工作。不同之处在于 launch 返回一个 Job 并且不附带任何结果值,而 async 返回一个 Deferred —— 一个轻量级的非阻塞 future, 这代表了一个将会在稍后提供结果的 promise。可以使用 .await() 在一个延期的值上得到它的最终结果, 但是 Deferred 也是一个 Job,所以如果需要的话,你可以取消它。

import kotlinx.coroutines.*
import kotlin.system.measureTimeMillisfun main() = runBlocking {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
}

运行结果
在这里插入图片描述
这里快了两倍,因为两个协程并发执行。 请注意,使用协程进行并发总是显式的。

惰性启动的 async

可选的,async 可以通过将 start 参数设置为 CoroutineStart.LAZY 而变为惰性的。 在这个模式下,只有结果通过 await 获取的时候协程才会启动,或者在 Job 的 start 函数调用的时候。运行下面的示例:

import kotlinx.coroutines.*
import kotlin.system.measureTimeMillisfun main() = runBlocking {val time = measureTimeMillis {val one = async(start = CoroutineStart.LAZY) { doSomethingUsefulOne() }val two = async(start = CoroutineStart.LAZY) { doSomethingUsefulTwo() }// 执行一些计算one.start() // 启动第一个two.start() // 启动第二个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
}

运行结果
在这里插入图片描述

因此,在先前的例子中这里定义的两个协程没有执行,但是控制权在于程序员准确的在开始执行时调用 start。我们首先 调用 one,然后调用 two,接下来等待这个协程执行完毕。

注意,如果我们只是在 println 中调用 await,而没有在单独的协程中调用 start,这将会导致顺序行为,直到 await 启动该协程 执行并等待至它结束,这并不是惰性的预期用例。 在计算一个值涉及挂起函数时,这个 async(start = CoroutineStart.LAZY) 的用例用于替代标准库中的 lazy 函数。

async 风格的函数

我们可以定义异步风格的函数来 异步 的调用 doSomethingUsefulOne 和 doSomethingUsefulTwo 并使用 async 协程建造器并带有一个显式的 GlobalScope 引用。 我们给这样的函数的名称中加上“……Async”后缀来突出表明:事实上,它们只做异步计算并且需要使用延期的值来获得结果。

import kotlinx.coroutines.*
import kotlin.system.measureTimeMillis// 注意,在这个示例中我们在 `main` 函数的右边没有加上 `runBlocking`
fun main() {val time = measureTimeMillis {// 我们可以在协程外面启动异步执行val one = somethingUsefulOneAsync()val two = somethingUsefulTwoAsync()// 但是等待结果必须调用其它的挂起或者阻塞// 当我们等待结果的时候,这里我们使用 `runBlocking { …… }` 来阻塞主线程runBlocking {println("The answer is ${one.await() + two.await()}")}}println("Completed in $time ms")
}//注意,这些 xxxAsync 函数不是 挂起 函数。它们可以在任何地方使用。
//然而,它们总是在调用它们的代码中意味着异步(这里的意思是 并发 )执行。// somethingUsefulOneAsync 函数的返回值类型是 Deferred<Int>
fun somethingUsefulOneAsync() = GlobalScope.async {doSomethingUsefulOne()
}// somethingUsefulTwoAsync 函数的返回值类型是 Deferred<Int>
fun somethingUsefulTwoAsync() = GlobalScope.async {doSomethingUsefulTwo()
}

这种带有异步函数的编程风格仅供参考,因为这在其它编程语言中是一种受欢迎的风格。在 Kotlin 的协程中使用这种风格是强烈不推荐的, 原因如下所述。

考虑一下如果 val one = somethingUsefulOneAsync() 这一行和 one.await() 表达式这里在代码中有逻辑错误, 并且程序抛出了异常以及程序在操作的过程中中止,将会发生什么。 通常情况下,一个全局的异常处理者会捕获这个异常,将异常打印成日记并报告给开发者,但是反之该程序将会继续执行其它操作。但是这里我们的 somethingUsefulOneAsync 仍然在后台执行, 尽管如此,启动它的那次操作也会被终止。这个程序将不会进行结构化并发,如下一小节所示。

使用 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.measureTimeMillisfun main() = runBlocking {val time = measureTimeMillis {println("The answer is ${concurrentSum()}")}println("Completed in $time ms")
}// 由于 async 被定义为了 CoroutineScope 上的扩展,我们需要将它写在作用域内,并且这是 coroutineScope 函数所提供的:
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
}

从上面的 main 函数的输出可以看出,我们仍然可以同时执行这两个操作:

运行结果
在这里插入图片描述

取消始终通过协程的层次结构来进行传递:

fun main() = runBlocking<Unit> {try {failedConcurrentSum()} catch (e: ArithmeticException) {println("Computation failed with ArithmeticException 异常信息:${e.toString()}")}//Second child throws an exception//First child was cancelled//Computation failed with ArithmeticException//请注意,如果其中一个子协程(即 two)失败,第一个 async 以及等待中的父协程都会被取消:
}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()
}

请注意,如果其中一个子协程(即 two)失败,第一个 async 以及等待中的父协程都会被取消:
运行结果
在这里插入图片描述

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

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

相关文章

【C语言基础】:操作符详解(二)

文章目录 操作符详解一、上期扩展二、单目操作符三、逗号表达式四、下标访问[]、 函数调用()五、结构成员访问操作符六、操作符的属性&#xff1a;优先级、结合性1. 优先级2. 结合性 操作符详解 上期回顾&#xff1a;【C语言基础】&#xff1a;操作符详解(一) 一、上期扩展 …

这家宠物品牌的内容运营怎么做的?太好玩儿了吧

养宠的朋友应该多多少少对“诚实一口”这个牌子有所耳闻&#xff0c;2018年诚实一口品牌正式立项&#xff0c;虽然不算经典品牌&#xff0c;但在国内也是小有名气的宠物品牌。今天媒介盒子想和大家聊的不是产品&#xff0c;而是想聊聊作为成立时间不长的国产宠粮品牌是如何凭借…

vant安装教程(基于vue3)

1、先安装 npm i vant 如果不行安装这个 yarn add vant 2、在main.js中引入即可 import { createApp } from vue import App from ./App.vue import router from ./router import store from ./store import { Button } from vant; import vant/lib/index.css;createApp(App).…

【Java程序设计】【C00307】基于Springboot的基Hadoop的物品租赁管理系统(有论文)

基于Springboot的基Hadoop的物品租赁管理系统&#xff08;有论文&#xff09; 项目简介项目获取开发环境项目技术运行截图 项目简介 这是一个基于Springboot的基于 Hadoop的物品租赁系统的设计与实现&#xff0c;本系统有管理员、用户二种角色权限&#xff1b; 前台首页&#…

Java+SpringBoot+Vue+MySQL构建银行客户管理新平台

✍✍计算机毕业编程指导师 ⭐⭐个人介绍&#xff1a;自己非常喜欢研究技术问题&#xff01;专业做Java、Python、微信小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。 ⛽⛽实战项目&#xff1a;有源码或者技术上的问题欢迎在评论区一起讨论交流&#xff01; ⚡⚡ Java、…

c++:vector的相关oj题(136. 只出现一次的数字、118. 杨辉三角、26. 删除有序数组中的重复项、JZ39 数组中出现次数超过一半的数字)

文章目录 1. 136. 只出现一次的数字题目详情代码(直接来异或&#xff09;思路 2. 118. 杨辉三角题目详情代码1思路代码2思路2 3. 26. 删除有序数组中的重复项题目详情代码思路 4. JZ39 数组中出现次数超过一半的数字题目详情代码1&#xff08;暴力&#xff09;思路1代码2&#…

linux c++ 开发 tensorrt 安装

tensorrt 官方下载地址&#xff08;需要注册账号登录&#xff09;&#xff1a;Log in | NVIDIA Developer 根据系统发行版和CUDA版本 (nvcc -V) 选择合适的安装包 EA&#xff08;early access&#xff09;版本代表抢先体验。 GA&#xff08;general availability&#xff09;代…

redis——客户端

Redis是一个典型一对多服务器程序&#xff0c;一个服务器可以与多个客户端进行网络连接&#xff0c;每隔客户端可以向服务器发送命令请求&#xff0c;而服务器则接收并处理客户端发送的命令请求&#xff0c;并向客户端返回命令请求。 通过是一个I/O多路复用技术实现的文件事件处…

U-Mail邮件系统反垃圾病毒解决方案

随着互联网的快速发展和广泛应用&#xff0c;人类正逐步地从工业社会迈入信息社会&#xff0c;网络也已经越来越成为生产经营活动的重要场所。例如电子邮件就已经企业内外部信息交流的重要工具&#xff0c;它的应用还可以提高企业办公效率&#xff0c;利于企业信息流通的系统化…

网络原理——HTTPS

HTTPS是 在HTTP的基础上&#xff0c;引入了一个加密层&#xff08;SSL)。 1. 为什么需要HTTPS 在我们使用浏览器下载一些软件时&#xff0c;相信大家都遇到过这种情况&#xff1a;明明这个链接显示的是下载A软件&#xff0c;点击下载时就变成了B软件&#xff0c;这种情况是运…

Java8 Stream API 详解:流式编程进行数据处理

&#x1f3f7;️个人主页&#xff1a;牵着猫散步的鼠鼠 &#x1f3f7;️系列专栏&#xff1a;Java全栈-专栏 &#x1f3f7;️个人学习笔记&#xff0c;若有缺误&#xff0c;欢迎评论区指正 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&…

【踩坑】修复报错 you should not try to import numpy from its source directory

转载请注明出处&#xff1a;小锋学长生活大爆炸[xfxuezhang.cn] 报错如下&#xff1a; 修复方法一&#xff1a; pip install pyinstaller5.9 修复方法二&#xff1a; pip install numpy1.24.1