默认顺序调用

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

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. (续)signal-slot:python版本的多进程通信的信号与槽机制(编程模式)的库(library) —— 强化学习ppo算法库sample-factory的多进程包装器,实现类似Qt的多进程编程模式(信号与槽机制) —— python3.12版本下成功通过测试

    前文: signal-slot:python版本的多进程通信的信号与槽机制(编程模式)的库(library) -- 强化学习ppo算法库sample-factory的多进程包装器,实现类似Qt的多进程 ...

  2. 《Python数据可视化之matplotlib实践》 源码 第二篇 精进 第六章

    图 6.1 import matplotlib.pyplot as plt import numpy as np x=np.linspace(-2*np.pi, 2*np.pi, 200) y=np. ...

  3. 并行化强化学习 —— 最终版本 —— 并行reinforce算法的尝试

    本文代码地址: https://gitee.com/devilmaycry812839668/final_-version_-parallelism_-reinforce_-cart-pole 结合了 ...

  4. 【转载】 arXiv论文提交流程

    原文地址: https://blog.csdn.net/u010705932/article/details/105834469 =================================== ...

  5. PyTorch显存机制分析

    参考: ======================================================= 在pytorch中有几个关于显存的关键词: 在pytorch中显存为缓存和变量分 ...

  6. php日常收获

    php 1.sprintf 用法(晚上写成blog w3cschool可查) 2.使用thinkphp getfield 方法时只查询一个字段默认返回第一条数据, 如果想要返回数组需要写成: $thi ...

  7. StartImage.DLL使用说明

    StartImage.DLL使用说明 一.库的引入 库包含以下物件,请按照要求将以下库映入到项目中 StartImage.dll StartImage.lib StartImage.h 二.注意事项 ...

  8. Atcoder ABC299 E-G

    Atcoder ABC299 E-G E - Nearest Black Vertex 链接: E - Nearest Black Vertex (atcoder.jp) 简要题意: 问题陈述 给你一 ...

  9. Linux 检查端口监听情况

    使用 lsof $ sudo lsof -i :22 COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME sshd 963 root 3u IPv4 ...

  10. 【图文教程】Centos 7下安装Hadoop

    环境说明: 系统:Centos7 在VM中安装的 hadoop版本:2.7.7 JDK:1.8 注意:Hadoop需要Java环境的.记得安装Java环境 PS:Centos JDK安装  mkdir ...