协程 组合挂起函数

协程 组合挂起函数

1.按默认的顺序执行

假设我们有两个挂起函数声明在任何地方,它做一些有用的事情像访问远程服务或者进行计算密集型操作。我们假设它上有意义的,但实际上,出于此示例的目的,每个都只是延迟一秒钟:

suspend fun doSomethingUsefulOne(): Int {
    delay(1000L) // pretend we are doing something useful here
    return 13
}

suspend fun doSomethingUsefulTwo(): Int {
    delay(1000L) // pretend we are doing something useful here, too
    return 29
}

如果我们需要它们按顺序调用,先执行doSomethingUsefulOne,再执行doSomethingUsefulTwo,再计算相加它们的结果,我们该怎样做?在实践中,如果我们使用第一个函数的结果来决定是否需要调用第二个函数或决定如何调用它,我们就会这样做。

我们实用一个寻常的顺序调用,因为代码在协程里面,就像在正常代码那样,按默认顺序执行。以下示例通过测量执行两个挂起函数所需的总时间来演示它:

import kotlinx.coroutines.*
import kotlin.system.*

fun main() = runBlocking<Unit> {
    val time = measureTimeMillis {
        val one = doSomethingUsefulOne()
        val two = doSomethingUsefulTwo()
        println("The answer is ${one + two}")
    }
    println("Completed in $time ms")    
}

suspend fun doSomethingUsefulOne(): Int {
    delay(1000L) // pretend we are doing something useful here
    return 13
}

suspend fun doSomethingUsefulTwo(): Int {
    delay(1000L) // pretend we are doing something useful here, too
    return 29
}

执行结果

The answer is 42
Completed in 2017 ms

2.用异步并发

如果调用的两个挂起函数的调用doSomethingUsefulOne和doSomethingUsefulTwo不互相依赖,我们想要最快得到结果,可以并发执行这两个函数吗?async的到来可以提供这些帮助
概念上,async和launch很像,它启动一个单独的协程,它是一个轻量级线程,可与所有其他协程同时工作。不同之处在于,launch返回一个job,不关心任何结果,而 async 返回一个 Deferred —— 一个轻量级的非阻塞未来,代表了稍后提供结果的承诺。您可以在延迟值上使用 .await() 来获得其最终结果,但是Deferred也是一个job,因此,你可以在需要的时候取消它。

import kotlinx.coroutines.*
import kotlin.system.*

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

suspend fun doSomethingUsefulOne(): Int {
    delay(1000L) // pretend we are doing something useful here
    return 13
}

suspend fun doSomethingUsefulTwo(): Int {
    delay(1000L) // pretend we are doing something useful here, too
    return 29
}

执行结果

The answer is 42
Completed in 1017 ms

这速度是原来的两倍,因为两个协程并发执行了。请注意,协同程序的并发性始终是显式的。

3.async懒启动

可选地,可以通过将其 start 参数设置为 CoroutineStart.LAZY 来使异步变得惰性。在这种模式下,它仅在 await 需要其结果或调用其 Job 的 start 函数时才启动协程。运行以下示例:

import kotlinx.coroutines.*
import kotlin.system.*

fun main() = runBlocking<Unit> {
    val time = measureTimeMillis {
        val one = async(start = CoroutineStart.LAZY) { doSomethingUsefulOne() }
        val two = async(start = CoroutineStart.LAZY) { doSomethingUsefulTwo() }
        // some computation
        one.start() // start the first one
        two.start() // start the second one
        println("The answer is ${one.await() + two.await()}")
    }
    println("Completed in $time ms")    
}

suspend fun doSomethingUsefulOne(): Int {
    delay(1000L) // pretend we are doing something useful here
    return 13
}

suspend fun doSomethingUsefulTwo(): Int {
    delay(1000L) // pretend we are doing something useful here, too
    return 29
}

运行结果

The answer is 42
Completed in 1017 ms

因此,这里定义了两个协程,但没有像前面的示例那样执行,但是通过调用 start 将控制权交给了程序员何时开始执行。我们先启动一个,然后启动两个,然后等待各个协程完成。
注意,如果我们调用了await方法在println方法中,而没有首先调用start方法,这将导致顺序行为,由于await执行协程并等待其完成,所以这不是懒加载的使用用例。async(start = CoroutineStart.LAZY)的用例是在数值的计算涉及暂停功能的情况下替代标准的懒惰函数。

4.异步风格函数

我们可以使用异步协程构建器使用 GlobalScope 引用来定义异步调用 doSomethingUsefulOne 和 doSomethingUsefulTwo 的异步样式函数,以选择退出结构化并发。我们用“…Async”后缀命名这些函数,以强调它们只启动异步计算并且需要使用生成的延迟值来获得结果这一事实。

GlobalScope 是一个微妙的 API,它可能会以非平凡的方式适得其反,其中一个将在下面解释,因此您必须明确选择使用带有 @OptIn(DelicateCoroutinesApi::class) 的 GlobalScope。

// The result type of somethingUsefulOneAsync is Deferred<Int>
@OptIn(DelicateCoroutinesApi::class)
fun somethingUsefulOneAsync() = GlobalScope.async {
    doSomethingUsefulOne()
}

// The result type of somethingUsefulTwoAsync is Deferred<Int>
@OptIn(DelicateCoroutinesApi::class)
fun somethingUsefulTwoAsync() = GlobalScope.async {
    doSomethingUsefulTwo()
}

请注意,这些 xxxAsync 函数不是挂起函数。它们可以在任何地方使用。然而,它们的使用总是意味着它们的动作与调用代码的异步(这里是指并发)执行。
以下示例显示了它们在协程之外的使用:

import kotlinx.coroutines.*
import kotlin.system.*

// note that we don't have `runBlocking` to the right of `main` in this example
fun main() {
    val time = measureTimeMillis {
        // we can initiate async actions outside of a coroutine
        val one = somethingUsefulOneAsync()
        val two = somethingUsefulTwoAsync()
        // but waiting for a result must involve either suspending or blocking.
        // here we use `runBlocking { ... }` to block the main thread while waiting for the result
        runBlocking {
            println("The answer is ${one.await() + two.await()}")
        }
    }
    println("Completed in $time ms")
}

@OptIn(DelicateCoroutinesApi::class)
fun somethingUsefulOneAsync() = GlobalScope.async {
    doSomethingUsefulOne()
}

@OptIn(DelicateCoroutinesApi::class)
fun somethingUsefulTwoAsync() = GlobalScope.async {
    doSomethingUsefulTwo()
}

suspend fun doSomethingUsefulOne(): Int {
    delay(1000L) // pretend we are doing something useful here
    return 13
}

suspend fun doSomethingUsefulTwo(): Int {
    delay(1000L) // pretend we are doing something useful here, too
    return 29
}

这里提供的这种带有异步函数的编程风格只是为了说明,因为它是其他编程语言中的流行风格。强烈建议不要在 Kotlin 协程中使用这种风格,原因如下所述。

考虑一下如果在 val one = somethingUsefulOneAsync() 行和 one.await() 表达式之间代码中有一些逻辑错误,并且程序抛出异常,并且程序正在执行的操作中止,会发生什么。通常,全局错误处理程序可以捕获此异常,记录并向开发人员报告错误,但程序可以继续执行其他操作。但是,这里somethingUsefulOneAsync 仍在后台运行,即使启动它的操作已中止。结构化并发不会发生此问题,如下节所示。

5.异步结构化并发

让我们以 Concurrent using async 示例为例,提取一个同时执行 doSomethingUsefulOne 和 doSomethingUsefulTwo 并返回它们结果之和的函数。因为异步协程构建器被定义为 CoroutineScope 的扩展,所以我们需要将它放在作用域中,这就是 coroutineScope 函数提供的:

suspend fun concurrentSum(): Int = coroutineScope {
    val one = async { doSomethingUsefulOne() }
    val two = async { doSomethingUsefulTwo() }
    one.await() + two.await()
}

这样,如果 concurrentSum 函数的代码内部出现问题并抛出异常,则在其范围内启动的所有协程都将被取消。

import kotlinx.coroutines.*
import kotlin.system.*

fun main() = runBlocking<Unit> {
    val time = measureTimeMillis {
        println("The answer is ${concurrentSum()}")
    }
    println("Completed in $time ms")    
}

suspend fun concurrentSum(): Int = coroutineScope {
    val one = async { doSomethingUsefulOne() }
    val two = async { doSomethingUsefulTwo() }
    one.await() + two.await()
}

suspend fun doSomethingUsefulOne(): Int {
    delay(1000L) // pretend we are doing something useful here
    return 13
}

suspend fun doSomethingUsefulTwo(): Int {
    delay(1000L) // pretend we are doing something useful here, too
    return 29
}

我们仍然可以同时执行这两个操作,从上面的 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) // Emulates very long computation
            42
        } finally {
            println("First child was cancelled")
        }
    }
    val two = async<Int> { 
        println("Second child throws an exception")
        throw ArithmeticException()
    }
    one.await() + two.await()
}

注意第一个异步和等待的父级是如何在一个子级(即两个)失败时取消的:

Second child throws an exception
First child was cancelled
Computation failed with ArithmeticException
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值