十年网站开发经验 + 多家企业客户 + 靠谱的建站团队
量身定制 + 运营维护+专业推广+无忧售后,网站问题一站解决
假设我们在不同的地⽅定义了两个进⾏某种调⽤远程服务或者进⾏计算的挂起函数。我们只假设它们都是有⽤的,但是实际上它们在这个⽰例中只是为了该⽬的⽽延迟了⼀秒钟:
成都创新互联公司自2013年创立以来,先为饶河等服务建站,饶河等地企业,进行企业商务咨询服务。为饶河企业网站制作PC+手机+微官网三网同步一站式服务解决您的所有建站问题。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注意,这些 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请注意,如果其中⼀个⼦协程(即 two )失败,第⼀个 async 以及等待中的⽗协程都会被取消:
Second child throws an exception First child was cancelled Computation failed with ArithmeticException