默认顺序调用

假设我们在不同的地⽅定义了两个进⾏某种调⽤远程服务或者进⾏计算的挂起函数。我们只假设它们都是有⽤的,但是实际上它们在这个⽰例中只是为了该⽬的⽽延迟了⼀秒钟:

suspend fun doSomethingUsefulOne(): Int {
delay(1000L) // 假设我们在这⾥做了⼀些有⽤的事
return 13
}
suspend fun doSomethingUsefulTwo(): Int {
delay(1000L) // 假设我们在这⾥也做了⼀些有⽤的事
return 29
}

  如果需要按 顺序 调⽤它们,我们接下来会做什么⸺⾸先调⽤ doSomethingUsefulOne 接下来 调 ⽤ doSomethingUsefulTwo ,并且计算它们结果的和吗?实际上,如果我们要根据第⼀个函数的结 果来决定是否我们需要调⽤第⼆个函数或者决定如何调⽤它时,我们就会这样做。 我们使⽤普通的顺序来进⾏调⽤,因为这些代码是运⾏在协程中的,只要像常规的代码⼀样 顺序 都是 默认的。下⾯的⽰例展⽰了测量执⾏两个挂起函数所需要的总时间:

val time = measureTimeMillis {
val one = doSomethingUsefulOne()
val two = doSomethingUsefulTwo()
println("The answer is ${one + two}")
}
println("Completed in $time ms")

  它的打印输出如下:

The answer is 42
Completed in 2017 ms

  

使用async并发

 如果 doSomethingUsefulOne 与 doSomethingUsefulTwo 之间没有依赖,并且我们想更快的得 到结果,让它们进⾏ 并发 吗?这就是 async 可以帮助我们的地⽅。 在概念上,async 就类似于 launch。它启动了⼀个单独的协程,这是⼀个轻量级的线程并与其它所有的 协程⼀起并发的⼯作。不同之处在于 launch 返回⼀个 Job 并且不附带任何结果值,⽽ async 返回 ⼀个 Deferred⸺⼀个轻量级的⾮阻塞 future,这代表了⼀个将会在稍后提供结果的 promise。你可 以使⽤ .await() 在⼀个延期的值上得到它的最终结果,但是 Deferred 也是⼀个 Job ,所以如果 需要的话,你可以取消它。

val time = measureTimeMillis {
val one = async { doSomethingUsefulOne() }
val two = async { doSomethingUsefulTwo() }
println("The answer is ${one.await() + two.await()}")
}
println("Completed in $time ms")

  它的打印输出如下:

The answer is 42
Completed in 1017 ms

  这⾥快了两倍,因为两个协程并发执⾏。请注意,使⽤协程进⾏并发总是显式的。

惰性启动的 async

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

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")

  它的打印输出如下:

The answer is 42
Completed in 1017 ms

  因此,在先前的例⼦中这⾥定义的两个协程没有执⾏,但是控制权在于程序员准确的在开始执⾏时调⽤ start。我们⾸先 调⽤ one ,然后调⽤ two ,接下来等待这个协程执⾏完毕。 注意,如果我们只是在 println 中调⽤ await,⽽没有在单独的协程中调⽤ start,这将会导致顺序⾏ 为,直到 await 启动该协程 执⾏并等待⾄它结束,这并不是惰性的预期⽤例。在计算⼀个值涉及挂起函 数时,这个 async(start = CoroutineStart.LAZY) 的⽤例⽤于替代标准库中的 lazy 函数。

async风格的函数

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

// somethingUsefulOneAsync 函数的返回值类型是 Deferred<Int>
fun somethingUsefulOneAsync() = GlobalScope.async {
doSomethingUsefulOne()
}
// somethingUsefulTwoAsync 函数的返回值类型是 Deferred<Int>
fun somethingUsefulTwoAsync() = GlobalScope.async {
doSomethingUsefulTwo()
}

  注意,这些 xxxAsync 函数不是 挂起 函数。它们可以在任何地⽅使⽤。然⽽,它们总是在调⽤它们的 代码中意味着异步(这⾥的意思是 并发 )执⾏。 下⾯的例⼦展⽰了它们在协程的外⾯是如何使⽤的:

// 注意,在这个⽰例中我们在 `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")
}

  这种带有异步函数的编程⻛格仅供参考,因为这在其它编程语⾔中是⼀种受欢迎的⻛格。在 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 函数内部发⽣了错误,并且它抛出了⼀个异常,所有在作⽤域 中启动的协程都会被取消。

val time = measureTimeMillis {
println("The answer is ${concurrentSum()}")
}
println("Completed in $time ms")

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

The answer is 42
Completed in 1017 ms

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

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()
}

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

Second child throws an exception
First child was cancelled
Computation failed with ArithmeticException

  

kotlin协程——>组合挂起函数的更多相关文章

  1. Kotlin协程解析系列(上):协程调度与挂起

    vivo 互联网客户端团队- Ruan Wen 本文是Kotlin协程解析系列文章的开篇,主要介绍Kotlin协程的创建.协程调度与协程挂起相关的内容 一.协程引入 Kotlin 中引入 Corout ...

  2. Kotlin 协程一 —— 全面了解 Kotlin 协程

    一.协程的一些前置知识 1.1 进程和线程 1.1.1基本定义 1.1.2为什么要有线程 1.1.3 进程与线程的区别 1.2 协作式与抢占式 1.2.1 协作式 1.2.2 抢占式 1.3 协程 二 ...

  3. Kotlin协程基础

    开发环境 IntelliJ IDEA 2021.2.2 (Community Edition) Kotlin: 212-1.5.10-release-IJ5284.40 我们已经通过第一个例子学会了启 ...

  4. Android Kotlin协程入门

    Android官方推荐使用协程来处理异步问题.以下是协程的特点: 轻量:单个线程上可运行多个协程.协程支持挂起,不会使正在运行协程的线程阻塞.挂起比阻塞节省内存,且支持多个并行操作. 内存泄漏更少:使 ...

  5. Kotlin协程第一个示例剖析及Kotlin线程使用技巧

    Kotlin协程第一个示例剖析: 上一次https://www.cnblogs.com/webor2006/p/11712521.html已经对Kotlin中的协程有了理论化的了解了,这次则用代码来直 ...

  6. Retrofit使用Kotlin协程发送请求

    Retrofit2.6开始增加了对Kotlin协程的支持,可以通过suspend函数进行异步调用.本文简单介绍一下Retrofit中协程的使用 导入依赖 app的build文件中加入: impleme ...

  7. rxjava回调地狱-kotlin协程来帮忙

    本文探讨的是在tomcat服务端接口编程中, 异步servlet场景下( 参考我另外一个文章),用rxjava来改造接口为全流程异步方式 好处不用说 tomcat的worker线程利用率大幅提高,接口 ...

  8. Kotlin协程重要概念详解【纯理论】

    在之前对Kotlin的反射进行了详细的学习,接下来进入一个全新的篇章,就是关于Koltin的协程[coroutine],在正式撸码之前先对它有一个全面理论化的了解: 协程的定义: 协和通过将复杂性放入 ...

  9. Kotlin协程入门

    开发环境 IntelliJ IDEA 2021.2.2 (Community Edition) Kotlin: 212-1.5.10-release-IJ5284.40 介绍Kotlin中的协程.用一 ...

  10. Kotlin协程通信机制: Channel

    Coroutines Channels Java中的多线程通信, 总会涉及到共享状态(shared mutable state)的读写, 有同步, 死锁等问题要处理. 协程中的Channel用于协程间 ...

随机推荐

  1. 【VMware】将NAT虚拟机开放访问

    NAT模式下面需要将主机内的虚拟机提供给外部访问 这个设置可以通过开启端口来实现外部访问NAT虚拟机 主机端口 - 映射 虚拟机 IP 的端口,问题是有多少个虚拟机应用就需要开多少个端口...

  2. 人形机器人 —— NVIDIA公司给出的操作算法(动态操作任务,dynamic manipulation tasks)(机械手臂/灵巧手)框架示意图 —— NVIDIA Isaac Manipulator

    原文: https://developer.nvidia.com/isaac/manipulator#foundation-models NVIDIA公司准备针对人形机器人的各部分操作分别推出一个AI ...

  3. MPI4PY的数据类型 —— MPI4Py data type

    原文地址: http://education.molssi.org/parallel-programming/03-distributed-examples-mpi4py/index.html MPI ...

  4. Apache DolphinScheduler如何开启开机自启动功能?

    转载自东华果汁哥 Apache DolphinScheduler 是一个分布式.去中心化的大数据工作流调度系统,支持大数据任务调度.若要设置 DolphinScheduler 开机自启动,通常需要将其 ...

  5. 神经网络之卷积篇:详解卷积步长(Strided convolutions)

    详解卷积步长 卷积中的步幅是另一个构建卷积神经网络的基本操作,让向展示一个例子. 如果想用3×3的过滤器卷积这个7×7的图像,和之前不同的是,把步幅设置成了2.还和之前一样取左上方的3×3区域的元素的 ...

  6. 附037.Kubernetes_v1.29.2高可用部署架构二

    部署组件 该 Kubernetes 部署过程中,对于部署环节,涉及多个组件,主要有 kubeadm .kubelet .kubectl. kubeadm介绍 Kubeadm 为构建 Kubernete ...

  7. RabbitMq高级特性之延迟队列 通俗易懂 超详细 【内含案例】

    RabbitMq高级特性之延迟队列 介绍 消息进入队列后不能立即被消费,到达指定时间后才可被消费 实现 结合以下两种即可达到延迟队列 RabbitMq高级特性之TTL过期时间 RabbitMq高级特性 ...

  8. Camera | 10.linux驱动 led架构-基于rk3568

    前面文章我们简单给大家介绍了如何移植闪光灯芯片sgm3141,该驱动依赖了led子系统和v4l2子系统. V4L2可以参考前面camera系列文章,本文主要讲述led子系统. 一.LED子系统框架 L ...

  9. 4. 从0开始学ARM-ARM指令,移位、数据处理、BL、机器码

    <到底什么是Cortex.ARMv8.arm架构.ARM指令集.soc?一文帮你梳理基础概念[科普]> 关于ARM指令用到的IDE开发环境可以参考下面这篇文章 <1. 从0开始学AR ...

  10. FFmpeg开发笔记(四十七)寒冬下安卓程序员的几个技术转型发展方向

    ​IT寒冬之下,程序员这个职业不再像以往那么吃香,尤其是APP开发的门槛越来越高,使得安卓程序员不得不求变,如果不在技术上及时转型提高,逆水行舟未来不可期呀. 有鉴于此,博主整理了几个可供安卓程序员的 ...