默认顺序调用

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

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. 【MySQL】MGR高可用搭建

    MySQL8.0.27如何安装 https://www.cnblogs.com/mindzone/p/15450312.html 部署过程中各种问题可参考的解决方案 我遇见的搭建问题,解决方案参考下面 ...

  2. 【ElasticSearch】02 查询操作

    准备样本: Elasticsearch 提供了基于 JSON 提供完整的查询 DSL 来定义查询 查询条件还适用于删除操作   创建索引: # PUT http://127.0.0.1:9200/st ...

  3. 【Java】Input,Output,Stream I/O流 02 文件流 & 缓冲流

    Reader & Writter 只适合文本的输入输出 [.txt .java .c .cpp] 传输文件,不能使用文本IO进行读写,需要使用文件输入输出流 public class IOTe ...

  4. 【Vue】Re13 CLI(Command Line Interface)

    一.What is CLI Command Line Interface 命令行接口 但是说是命令行界面,在官方又被称为脚手架 一个单词三个意思,所以令人困惑 但是根据实际意义用途来说就是帮助开发者更 ...

  5. 学术写作: These authors contributed equally to this work. —— 共同一作

    早些年很少看到论文里面有: These authors contributed equally to this work. 不过现在这种方法在论文中出现的还是比较多的,说白了,这种共同一作的声明其实是 ...

  6. 洛谷P1209修理牛棚 Barn Repair

    [USACO1.3] 修理牛棚 Barn Repair 题目描述 在一个月黑风高的暴风雨夜,Farmer John 的牛棚的屋顶.门被吹飞了 好在许多牛正在度假,所以牛棚没有住满. 牛棚一个紧挨着另一 ...

  7. 用海豚调度器定时调度从Kafka到HDFS的kettle任务脚本

    在实际项目中,从Kafka到HDFS的数据是每天自动生成一个文件,按日期区分.而且Kafka在不断生产数据,因此看看kettle是不是需要时刻运行?能不能按照每日自动生成数据文件? 为了测试实际项目中 ...

  8. JavaScript设计模式样例十八 —— 命令模式

    命令模式(Command Pattern) 定义:请求以命令的形式包裹在对象中,并传给调用对象.调用对象寻找可以处理该命令的合适的对象,并把该命令传给相应的对象,该对象执行命令.目的:将一个请求封装成 ...

  9. Java 读取 IP 地址

    使用 InetAddress 类 可以利用 Java 自带的 InetAddress 类来检查一个字符串是否为有效的 IP 地址: import java.net.InetAddress; // 导入 ...

  10. JWT(JSON WEB TOKEN)是玩具吗

    JWT当然不是玩具,理解其设计意图,和适用场景自然会发现存在的就是有价值的 JWT: JSON Web Token 起源和定义 JWT(JSON Web Token)是由 IETF(Internet ...