**目录**
[TOC]
## 组合挂起函数
本节介绍了将挂起函数组合的各种方法。
### 默认顺序调用
假设我们在不同的地方定义了两个进行某种调用远程服务或者进行计算的挂起函数。我们只假设它们都是有用的,但是实际上它们在这个示例中只是为了该目的而延迟了一秒钟:
```kotlin
suspend fun doSomethingUsefulOne(): Int {
delay(1000L) // 假设我们在这里做了一些有用的事
return 13
}
suspend fun doSomethingUsefulTwo(): Int {
delay(1000L) // 假设我们在这里也做了一些有用的事
return 29
}
```
如果需要按 _顺序_ 调用它们,我们接下来会做什么——首先调用 `doSomethingUsefulOne` _接下来_调用 `doSomethingUsefulTwo`,并且计算它们结果的和吗?实际上,如果我们要根据第一个函数的结果来决定是否我们需要调用第二个函数或者决定如何调用它时,我们就会这样做。
我们使用普通的顺序来进行调用,因为这些代码是运行在协程中的,只要像常规的代码一样 _顺序_ 都是默认的。下面的示例展示了测量执行两个挂起函数所需要的总时间:
```kotlin
import kotlinx.coroutines.*
import kotlin.system.*
fun main() = runBlocking<Unit> {
//sampleStart
val time = measureTimeMillis {
val one = doSomethingUsefulOne()
val two = doSomethingUsefulTwo()
println("The answer is ${one + two}")
}
println("Completed in $time ms")
//sampleEnd
}
suspend fun doSomethingUsefulOne(): Int {
delay(1000L) // 假设我们在这里做了些有用的事
return 13
}
suspend fun doSomethingUsefulTwo(): Int {
delay(1000L) // 假设我们在这里也做了一些有用的事
return 29
}
```
> 可以在[这里](https://github.com/hltj/kotlinx.coroutines-cn/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-compose-01.kt)获取完整代码。
它的打印输出如下:
```text
The answer is 42
Completed in 2017 ms
```
### 使用 async 并发
如果 `doSomethingUsefulOne` 与 `doSomethingUsefulTwo` 之间没有依赖,并且我们想更快的得到结果,让它们进行 _并发_ 吗?这就是 [async](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/async.html) 可以帮助我们的地方。
在概念上,[async](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/async.html) 就类似于 [launch](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/launch.html)。它启动了一个单独的协程,这是一个轻量级的线程并与其它所有的协程一起并发的工作。不同之处在于 `launch` 返回一个 [Job](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/index.html) 并且不附带任何结果值,而 `async` 返回一个 [Deferred](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-deferred/index.html) —— 一个轻量级的非阻塞 future,这代表了一个将会在稍后提供结果的 promise。你可以使用 `.await()` 在一个延期的值上得到它的最终结果,但是 `Deferred` 也是一个 `Job`,所以如果需要的话,你可以取消它。
```kotlin
import kotlinx.coroutines.*
import kotlin.system.*
fun main() = runBlocking<Unit> {
//sampleStart
val time = measureTimeMillis {
val one = async { doSomethingUsefulOne() }
val two = async { doSomethingUsefulTwo() }
println("The answer is ${one.await() + two.await()}")
}
println("Completed in $time ms")
//sampleEnd
}
suspend fun doSomethingUsefulOne(): Int {
delay(1000L) // 假设我们在这里做了些有用的事
return 13
}
suspend fun doSomethingUsefulTwo(): Int {
delay(1000L) // 假设我们在这里也做了些有用的事
return 29
}
```
> 可以在[这里](https://github.com/hltj/kotlinx.coroutines-cn/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-compose-02.kt)获取完整代码。
它的打印输出如下:
```text
The answer is 42
Completed in 1017 ms
```
这里快了两倍,因为两个协程并发执行。请注意,使用协程进行并发总是显式的。
### 惰性启动的 async
可选的,[async](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/async.html) 可以通过将 `start` 参数设置为 [CoroutineStart.LAZY](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-start/-l-a-z-y.html) 而变为惰性的。在这个模式下,只有结果通过 [await](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-deferred/await.html)获取的时候协程才会启动,或者在 `Job` 的 [start](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/start.html)函数调用的时候。运行下面的示例:
```kotlin
import kotlinx.coroutines.*
import kotlin.system.*
fun main() = runBlocking<Unit> {
//sampleStart
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")
//sampleEnd
}
suspend fun doSomethingUsefulOne(): Int {
delay(1000L) // 假设我们在这里做了些有用的事
return 13
}
suspend fun doSomethingUsefulTwo(): Int {
delay(1000L) // 假设我们在这里也做了些有用的事
return 29
}
```
> 可以在[这里](https://github.com/hltj/kotlinx.coroutines-cn/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-compose-03.kt)获取完整代码。
它的打印输出如下:
```text
The answer is 42
Completed in 1017 ms
```
因此,在先前的例子中这里定义的两个协程没有执行,但是控制权在于程序员准确的在开始执行时调用 [start](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/start.html)。我们首先调用 `one`,然后调用 `two`,接下来等待这个协程执行完毕。
注意,如果我们只是在 `println` 中调用 [await](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-deferred/await.html),而没有在单独的协程中调用 [start](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/start.html),这将会导致顺序行为,直到 [await](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-deferred/await.html)启动该协程执行并等待至它结束,这并不是惰性的预期用例。在计算一个值涉及挂起函数时,这个 `async(start = CoroutineStart.LAZY)` 的用例用于替代标准库中的 `lazy` 函数。
### async 风格的函数
我们可以定义异步风格的函数来 _异步_ 的调用 `doSomethingUsefulOne` 和 `doSomethingUsefulTwo`并使用 [async](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/async.html) 协程建造器并带有一个显式的 [GlobalScope](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-global-scope/index.html) 引用。我们给这样的函数的名称中加上“……Async”后缀来突出表明:事实上,它们只做异步计算并且需要使用延期的值来获得结果。
```kotlin
// somethingUsefulOneAsync 函数的返回值类型是 Deferred<Int>
fun somethingUsefulOneAsync() = GlobalScope.async {
doSomethingUsefulOne()
}
// somethingUsefulTwoAsync 函数的返回值类型是 Deferred<Int>
fun somethingUsefulTwoAsync() = GlobalScope.async {
doSomethingUsefulTwo()
}
```
注意,这些 `xxxAsync` 函数**不是** _挂起_ 函数。它们可以在任何地方使用。然而,它们总是在调用它们的代码中意味着异步(这里的意思是 _并发_ )执行。
下面的例子展示了它们在协程的外面是如何使用的:
```kotlin
import kotlinx.coroutines.*
import kotlin.system.*
//sampleStart
// 注意,在这个示例中我们在 `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")
}
//sampleEnd
fun somethingUsefulOneAsync() = GlobalScope.async {
doSomethingUsefulOne()
}
fun somethingUsefulTwoAsync() = GlobalScope.async {
doSomethingUsefulTwo()
}
suspend fun doSomethingUsefulOne(): Int {
delay(1000L) // 假设我们在这里做了些有用的事
return 13
}
suspend fun doSomethingUsefulTwo(): Int {
delay(1000L) // 假设我们在这里也做了些有用的事
return 29
}
```
> 可以在[这里](https://github.com/hltj/kotlinx.coroutines-cn/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-compose-04.kt)获取完整代码。
```
The answer is 42
Completed in 1085 ms
```
> 这种带有异步函数的编程风格仅供参考,因为这在其它编程语言中是一种受欢迎的风格。在 Kotlin 的协程中使用这种风格是**强烈不推荐**的,原因如下所述。
考虑一下如果 `val one = somethingUsefulOneAsync()` 这一行和 `one.await()` 表达式这里在代码中有逻辑错误,
并且程序抛出了异常以及程序在操作的过程中中止,将会发生什么。通常情况下,一个全局的异常处理者会捕获这个异常,将异常打印成日记并报告给开发者,但是反之该程序将会继续执行其它操作。但是这里我们的`somethingUsefulOneAsync` 仍然在后台执行,尽管如此,启动它的那次操作也会被终止。这个程序将不会进行结构化并发,如下一小节所示。
### 使用 async 的结构化并发
让我们使用[使用 async 的并发](http://www.kotlincn.net/docs/reference/coroutines/composing-suspending-functions.html#%E4%BD%BF%E7%94%A8-async-%E7%9A%84%E7%BB%93%E6%9E%84%E5%8C%96%E5%B9%B6%E5%8F%91)这一小节的例子并且提取出一个函数并发的调用 `doSomethingUsefulOne` 与 `doSomethingUsefulTwo` 并且返回它们两个的结果之和。
由于 [async](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/async.html) 被定义为了 [CoroutineScope](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/coroutine-scope.html) 上的扩展,我们需要将它写在作用域内,并且这是 [coroutineScope](https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/coroutine-scope.html) 函数所提供的:
```kotlin
suspend fun concurrentSum(): Int = coroutineScope {
val one = async { doSomethingUsefulOne() }
val two = async { doSomethingUsefulTwo() }
one.await() + two.await()
}
```
这种情况下,如果在 `concurrentSum` 函数内部发生了错误,并且它抛出了一个异常,所有在作用域中启动的协程都会被取消。
```kotlin
import kotlinx.coroutines.*
import kotlin.system.*
fun main() = runBlocking<Unit> {
//sampleStart
val time = measureTimeMillis {
println("The answer is ${concurrentSum()}")
}
println("Completed in $time ms")
//sampleEnd
}
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
}
```
> 可以在[这里](https://github.com/hltj/kotlinx.coroutines-cn/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-compose-05.kt)获取完整代码。
从上面的 `main` 函数的输出可以看出,我们仍然可以同时执行这两个操作:
```text
The answer is 42
Completed in 1017 ms
```
取消始终通过协程的层次结构来进行传递:
```kotlin
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()
}
```
> 可以在[这里](https://github.com/hltj/kotlinx.coroutines-cn/blob/master/kotlinx-coroutines-core/jvm/test/guide/example-compose-06.kt)获取完整代码。
请注意,如果其中一个子协程(即 `two`)失败,第一个 `async` 以及等待中的父协程都会被取消:
```text
Second child throws an exception
First child was cancelled
Computation failed with ArithmeticException
```
[async]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/async.html
[launch]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/launch.html
[Job]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/index.html
[Deferred]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-deferred/index.html
[CoroutineStart.LAZY]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-start/-l-a-z-y.html
[Deferred.await]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-deferred/await.html
[Job.start]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-job/start.html
[GlobalScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-global-scope/index.html
[CoroutineScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/-coroutine-scope/index.html
[coroutineScope]: https://kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-core/kotlinx.coroutines/coroutine-scope.html
- 前言
- Kotlin简介
- IntelliJ IDEA技巧总结
- idea设置类注释和方法注释模板
- 像Android Studion一样创建工程
- Gradle
- Gradle入门
- Gradle进阶
- 使用Gradle创建一个Kotlin工程
- 环境搭建
- Androidstudio平台搭建
- Eclipse的Kotlin环境配置
- 使用IntelliJ IDEA
- Kotlin学习路线
- Kotlin官方中文版文档教程
- 概述
- kotlin用于服务器端开发
- kotlin用于Android开发
- kotlin用于JavaScript开发
- kotlin用于原生开发
- Kotlin 用于数据科学
- 协程
- 多平台
- 新特性
- 1.1的新特性
- 1.2的新特性
- 1.3的新特性
- 开始
- 基本语法
- 习惯用法
- 编码规范
- 基础
- 基本类型
- 包与导入
- 控制流
- 返回与跳转
- 类与对象
- 类与继承
- 属性与字段
- 接口
- 可见性修饰符
- 扩展
- 数据类
- 密封类
- 泛型
- 嵌套类
- 枚举类
- 对象
- 类型别名
- 内嵌类
- 委托
- 委托属性
- 函数与Lambda表达式
- 函数
- Lambda表达式
- 内联函数
- 集合
- 集合概述
- 构造集合
- 迭代器
- 区间与数列
- 序列
- 操作概述
- 转换
- 过滤
- 加减操作符
- 分组
- 取集合的一部分
- 取单个元素
- 排序
- 聚合操作
- 集合写操作
- List相关操作
- Set相关操作
- Map相关操作
- 多平台程序设计
- 平台相关声明
- 以Gradle创建
- 更多语言结构
- 解构声明
- 类型检测与转换
- This表达式
- 相等性
- 操作符重载
- 空安全
- 异常
- 注解
- 反射
- 作用域函数
- 类型安全的构造器
- Opt-in Requirements
- 核心库
- 标准库
- kotlin.test
- 参考
- 关键字与操作符
- 语法
- 编码风格约定
- Java互操作
- Kotlin中调用Java
- Java中调用Kotlin
- JavaScript
- 动态类型
- kotlin中调用JavaScript
- JavaScript中调用kotlin
- JavaScript模块
- JavaScript反射
- JavaScript DCE
- 原生
- 并发
- 不可变性
- kotlin库
- 平台库
- 与C语言互操作
- 与Object-C及Swift互操作
- CocoaPods集成
- Gradle插件
- 调试
- FAQ
- 协程
- 协程指南
- 基础
- 取消与超时
- 组合挂起函数
- 协程上下文与调度器
- 异步流
- 通道
- 异常处理与监督
- 共享的可变状态与并发
- Select表达式(实验性)
- 工具
- 编写kotlin代码文档
- 使用Kapt
- 使用Gradle
- 使用Maven
- 使用Ant
- Kotlin与OSGI
- 编译器插件
- 编码规范
- 演进
- kotlin语言演进
- 不同组件的稳定性
- kotlin1.3的兼容性指南
- 常见问题
- FAQ
- 与Java比较
- 与Scala比较(官方已删除)
- Google开发者官网简介
- Kotlin and Android
- Get Started with Kotlin on Android
- Kotlin on Android FAQ
- Android KTX
- Resources to Learn Kotlin
- Kotlin样品
- Kotlin零基础到进阶
- 第一阶段兴趣入门
- kotlin简介和学习方法
- 数据类型和类型系统
- 入门
- 分类
- val和var
- 二进制基础
- 基础
- 基本语法
- 包
- 示例
- 编码规范
- 代码注释
- 异常
- 根类型“Any”
- Any? 可空类型
- 可空性的实现原理
- kotlin.Unit类型
- kotlin.Nothing类型
- 基本数据类型
- 数值类型
- 布尔类型
- 字符型
- 位运算符
- 变量和常量
- 语法和运算符
- 关键字
- 硬关键字
- 软关键字
- 修饰符关键字
- 特殊标识符
- 操作符和特殊符号
- 算术运算符
- 赋值运算符
- 比较运算符
- 逻辑运算符
- this关键字
- super关键字
- 操作符重载
- 一元操作符
- 二元操作符
- 字符串
- 字符串介绍和属性
- 字符串常见方法操作
- 字符串模板
- 数组
- 数组介绍创建及遍历
- 数组常见方法和属性
- 数组变化以及下标越界问题
- 原生数组类型
- 区间
- 正向区间
- 逆向区间
- 步长
- 类型检测与类型转换
- is、!is、as、as-运算符
- 空安全
- 可空类型变量
- 安全调用符
- 非空断言
- Elvis操作符
- 可空性深入
- 可空性和Java
- 函数
- 函数式编程概述
- OOP和FOP
- 函数式编程基本特性
- 组合与范畴
- 在Kotlin中使用函数式编程
- 函数入门
- 函数作用域
- 函数加强
- 命名参数
- 默认参数
- 可变参数
- 表达式函数体
- 顶层、嵌套、中缀函数
- 尾递归函数优化
- 函数重载
- 控制流
- if表达式
- when表达式
- for循环
- while循环
- 循环中的 Break 与 continue
- return返回
- 标签处返回
- 集合
- list集合
- list集合介绍和操作
- list常见方法和属性
- list集合变化和下标越界
- set集合
- set集合介绍和常见操作
- set集合常见方法和属性
- set集合变换和下标越界
- map集合
- map集合介绍和常见操作
- map集合常见方法和属性
- map集合变换
- 集合的函数式API
- map函数
- filter函数
- “ all ”“ any ”“ count ”和“ find ”:对集合应用判断式
- 别样的求和方式:sumBy、sum、fold、reduce
- 根据人的性别进行分组:groupBy
- 扁平化——处理嵌套集合:flatMap、flatten
- 惰性集合操作:序列
- 区间、数组、集合之间转换
- 面向对象
- 面向对象-封装
- 类的创建及属性方法访问
- 类属性和字段
- 构造器
- 嵌套类(内部类)
- 枚举类
- 枚举类遍历&枚举常量常用属性
- 数据类
- 密封类
- 印章类(密封类)
- 面向对象-继承
- 类的继承
- 面向对象-多态
- 抽象类
- 接口
- 接口和抽象类的区别
- 面向对象-深入
- 扩展
- 扩展:为别的类添加方法、属性
- Android中的扩展应用
- 优化Snackbar
- 用扩展函数封装Utils
- 解决烦人的findViewById
- 扩展不是万能的
- 调度方式对扩展函数的影响
- 被滥用的扩展函数
- 委托
- 委托类
- 委托属性
- Kotlin5大内置委托
- Kotlin-Object关键字
- 单例模式
- 匿名类对象
- 伴生对象
- 作用域函数
- let函数
- run函数
- with函数
- apply函数
- also函数
- 标准库函数
- takeIf 与 takeUnless
- 第二阶段重点深入
- Lambda编程
- Lambda成员引用高阶函数
- 高阶函数
- 内联函数
- 泛型
- 泛型的分类
- 泛型约束
- 子类和子类型
- 协变与逆变
- 泛型擦除与实化类型
- 泛型类型参数
- 泛型的背后:类型擦除
- Java为什么无法声明一个泛型数组
- 向后兼容的罪
- 类型擦除的矛盾
- 使用内联函数获取泛型
- 打破泛型不变
- 一个支持协变的List
- 一个支持逆变的Comparator
- 协变和逆变
- 第三阶段难点突破
- 注解和反射
- 声明并应用注解
- DSL
- 协程
- 协程简介
- 协程的基本操作
- 协程取消
- 管道
- 慕课霍丙乾协程笔记
- Kotlin与Java互操作
- 在Kotlin中调用Java
- 在Java中调用Kotlin
- Kotlin与Java中的操作对比
- 第四阶段专题练习
- 朱凯Kotlin知识点总结
- Kotlin 基础
- Kotlin 的变量、函数和类型
- Kotlin 里那些「不是那么写的」
- Kotlin 里那些「更方便的」
- Kotlin 进阶
- Kotlin 的泛型
- Kotlin 的高阶函数、匿名函数和 Lambda 表达式
- Kotlin协程
- 初识
- 进阶
- 深入
- Kotlin 扩展
- 会写「18.dp」只是个入门——Kotlin 的扩展函数和扩展属性(Extension Functions / Properties)
- Kotlin实战-开发Android