本节介绍了将挂起函数组合的各种方法。
默认顺序调用
假设我们在不同的地方定义了两个进行某种调用远程服务或者进行计算的挂起函数。我们只假设它们都是有用的,但是实际上它们在这个示例中只是为了该目的而延迟了一秒钟:
suspend fun doSomethingUsefulOne(): Int {
delay(1000L) // 假设我们在这里做了一些有用的事
return 13
}
suspend fun doSomethingUsefulTwo(): Int {
delay(1000L) // 假设我们在这里也做了一些有用的事
return 29
}
如果需要按 顺序 调用它们,我们接下来会做什么⸺首先调用 doSomethingUsefulOne 接下来 调
用 doSomethingUsefulTwo ,并且计算它们结果的和吗? 实际上,如果我们要根据第一个函数的结
果来决定是否我们需要调用第二个函数或者决定如何调用它时,我们就会这样做。
fun main16() = runBlocking<Unit> {
val time = measureTimeMillis {
val one = doSomethingUsefulOne()
val two = doSomethingUsefulTwo()
println("CoroutineTest The answer is ${one + two}")
}
println("CoroutineTest Completed in $time ms")
}
执行结果如下:
2020-05-13 10:52:52.384 8384-8384/com.yy.kotlindemo I/System.out: CoroutineTest The answer is 42
2020-05-13 10:52:52.384 8384-8384/com.yy.kotlindemo I/System.out: CoroutineTest Completed in 2052 ms
使用 async 并发
如果 doSomethingUsefulOne 与 doSomethingUsefulTwo 之间没有依赖,并且我们想更快的得到结果,让它们进行 并发 吗?这就是 async 可以帮助我们的地方。在概念上,async 就类似于 launch。它启动了一个单独的协程,这是一个轻量级的线程并与其它所有的协程一起并发的工作。不同之处在于 launch 返回一个 Job 并且不附带任何结果值,而 async 返回一个 Deferred ⸺ 一个轻量级的非阻塞 future, 这代表了一个将会在稍后提供结果的 promise。你可
以使用 .await() 在一个延期的值上得到它的最终结果, 但是 Deferred 也是一个 Job ,所以如果需要的话,你可以取消它。
promise [ˈprɒmɪs]
n. 许诺,允诺;希望
vt. 允诺,许诺;给人以…的指望或希望
vi. 许诺;有指望,有前途
future [ˈfjuːtʃə(r)]
n. 未来;前途;期货;将来时
adj. 将来的,未来的
deferred [dɪ'fɜːd]
adj. 延期的
v. 推迟(defer的过去式及过去分词形式)
fun main17() = 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")
}
输出结果如下:
2020-05-13 11:49:24.095 15013-15013/com.yy.kotlindemo I/System.out: The answer is 42
2020-05-13 11:49:24.095 15013-15013/com.yy.kotlindemo I/System.out: Completed in 1061 ms
这里快了两倍,因为两个协程并发执行。 请注意,使用协程进行并发总是显式的。
惰性启动的 async
可选的,async 可以通过将 start 参数设置为 CoroutineStart.LAZY 而变为惰性的。 在这个模式下,只有结果通过 await 获取的时候协程才会启动,或者在 Job 的 start 函数调用的时候。运行下面的示例:
fun main18() = 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")
}
执行结果如下:
2020-05-13 11:58:58.246 16289-16289/com.yy.kotlindemo I/System.out: The answer is 42
2020-05-13 11:58:58.247 16289-16289/com.yy.kotlindemo I/System.out: Completed in 1038 ms
因此,在先前的例子中这里定义的两个协程没有执行,但是控制权在于程序员准确的在开始执行时调用start。我们首先 调用 one ,然后调用 two ,接下来等待这个协程执行完毕。
注意,如果我们只是在 println 中调用 await,而没有在单独的协程中调用 start,这将会导致顺序行为,直到 await 启动该协程 执行并等待至它结束,这并不是惰性的预期用例。 在计算一个值涉及挂起函数时,这个
async(start = CoroutineStart.LAZY) 的用例用于替代标准库中的 lazy 函数。
async ⻛格的函数
我们可以定义异步⻛格的函数来 异步 的调用 doSomethingUsefulOne 和doSomethingUsefulTwo 并使用 async 协程建造器并带有一个显式的 GlobalScope 引用。 我们给这样的函数的名称中加上“......Async”后缀来突出表明:事实上,它们只做异步计算并且需要使用延期的值来获得结果。
fun somethingUsefulOneAsync() = GlobalScope.async {
doSomethingUsefulOne()
}
fun somethingUsefulTwoAsync() = GlobalScope.async {
doSomethingUsefulTwo()
}
注意,这些 xxxAsync 函数不是 挂起 函数。它们可以在任何地方使用。 然而,它们总是在调用它们的代码中意味着异步(这里的意思是 并发 )执行。
下面的例子展示了它们在协程的外面是如何使用的:
// note that we don't have `runBlocking` to the right of `main` in this example
fun main19() {
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")
}
执行结果如下:
2020-05-13 13:36:50.951 26233-26233/com.yy.kotlindemo I/System.out: The answer is 42
2020-05-13 13:36:50.951 26233-26233/com.yy.kotlindemo I/System.out: Completed in 1056 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 函数内部发生了错误,并且它抛出了一个异常, 所有在作用域中启动的协程都会被取消。
fun main20() = runBlocking<Unit> {
val time = measureTimeMillis {
println("The answer is ${concurrentSum()}")
}
println("Completed in $time ms")
}
从上面的 main 函数的输出可以看出,我们仍然可以同时执行这两个操作:
2020-05-14 13:44:35.721 23091-23091/com.yy.kotlindemo I/System.out: The answer is 42
2020-05-14 13:44:35.721 23091-23091/com.yy.kotlindemo I/System.out: Completed in 1060 ms
取消始终通过协程的层次结构来进行传递:
2020-05-14 13:54:29.451 24401-24401/com.yy.kotlindemo I/System.out: Second child throws an exception
2020-05-14 13:54:29.455 24401-24401/com.yy.kotlindemo I/System.out: First child was cancelled
2020-05-14 13:54:29.456 24401-24401/com.yy.kotlindemo I/System.out: Computation failed with
ArithmeticException
网友评论