默认顺序调用

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

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. 【ActiveJdbc】03

    一.查询API 简单条件筛选: List<Person> list = Person.where("name = 'John'") 动态参数条件: List<Pe ...

  2. 哈哈哈,我就说未来要研发无人的AI潜艇嘛 —— 说啥来啥 —— AI驱动的无人潜艇

    相关: 沉默5个月后,美国对华发出挑战书,万没想到,中方打法早就变了

  3. 【转载】 DeepMind 提出元梯度强化学习算法,显著提高大规模深度强化学习应用的性能

    原文地址: https://www.jiqizhixin.com/articles/053104 李亚洲翻译 2018/05/31 12:38 Pedro 路参与 ================== ...

  4. 图扑 HT for Web 轻松构建组态拓扑结构

      在现代的数据可视化和网络管理中,拓扑图是一种非常重要的工具.它可以直观地展示节点(Node)和节点之间的关系(Edge).无论是在 2D 还是 3D 环境中,拓扑图都可以帮助我们更好地理解和管理复 ...

  5. 如何不用加法符号计算a+b 的值?

    目前为止只有一种思路:位运算+递归小操作 a+b的值可以等价于a^b+(a&b)<<1,也就是a异或b的值加上a与b的值再左移一位.a异或b的值被叫做非进位求和,(a&b) ...

  6. BOM 相关知识总结

    一:介绍BOM 1 1.什么是BOM? 2 DOM就是一套操作HTML标签的API(接口/方法/属性) 3 BOM就是一套操作浏览器的API(接口/方法/属性) 4 5 2.BOM中常见的对象 6 w ...

  7. NetCore消息管道 Middleware

    中间件定义 /// <summary> /// 自定义中间件1 /// </summary> public class MyMiddleware : IMiddleware { ...

  8. 《HelloGitHub》第 101 期

    兴趣是最好的老师,HelloGitHub 让你对编程感兴趣! 简介 HelloGitHub 分享 GitHub 上有趣.入门级的开源项目. github.com/521xueweihan/HelloG ...

  9. 【YashanDB知识库】应用绑定参数的慢查询,慢日志抓取不到

    [问题分类]功能使用 [关键字]慢日志.绑定参数 [问题描述]应用下发的绑定参数的SQL,慢日志功能抓取不到查询耗时超过阈值的执行SQL [问题原因分析]慢日志的功能没有考虑绑定参数的情况 [解决/规 ...

  10. 傅里叶级数 傅里叶变换 FFT 时域 频域 功率谱 能量谱 功率谱密度PSD

    傅立叶级数是基于周期函数的,如果我们把周期推广到