首发于公众号: DSGtalk1989
31.协程挂起函数的组合
-
同步与并发
通常情况下,协程中的挂起函数都是同步执行的,执行完一个执行另一个,我们举个例子,作如下的两种计算。
suspend fun doSomethingUsefulOne(): Int { delay(1000L) // 假设我们在这里做了某些有用的工作 return 13 } suspend fun doSomethingUsefulTwo(): Int { delay(1000L) // 假设我们在这里也做了某些有用的工作 return 29 } fun main() = runBlocking<Unit> { //sampleStart val time = measureTimeMillis { val one = doSomethingUsefulOne() val two = doSomethingUsefulTwo() println("The answer is ${one + two}") } println("Completed in $time ms") //sampleEnd }这里出现了一个
measureTimeMillis方法,这个方法会返回一个long,表示这个协程的消耗时间。最终打印是这样的。The answer is 42 Completed in 2017 ms这时候,如果我们希望两个任务是并发的,需要使用到
async。我们写一个
async{},然后点进去看一下。public fun <T> CoroutineScope.async( context: CoroutineContext = EmptyCoroutineContext, start: CoroutineStart = CoroutineStart.DEFAULT, block: suspend CoroutineScope.() -> T ): Deferred<T>跟之前展示的
launch除了一个返回的是Job一个返回的是Deferred,其他是一模一样。进去一看/** * Deferred value is a non-blocking cancellable future — it is a [Job] that has a result */ public interface Deferred<out T> : Job是一个实现了
Job的接口,注释中说到是一个不会阻塞的,可以被取消的future,是一个有结果的Job。看上去可以在未来的某个时间点执行,而非立马执行的,且是异步的。在
Job的基础上多了3个方法和一个属性。public suspend fun await(): T public val onAwait: SelectClause1<T> public fun getCompleted(): T public fun getCompletionExceptionOrNull(): Throwable?下面两个都是实验性质的,我们就不看了主要是为了去拿异步协程的结果的。而现在本身可以直接通过
await也可以拿到结果。并且不会将协程再跑一遍。我们对上面的部分代码略做改动,其他保持不变。
val one = async { doSomethingUsefulOne() } val two = async { doSomethingUsefulTwo() } println("The answer is ${one.await() + two.await()}")打印出来的结果是
The answer is 42 Completed in 1017 ms很明显的两个挂起的函数变成了异步。
如果我们希望只有调用了
await方法才会开始启动协程,而不是在定义的时候就立马启动,可以针对async方法传入start参数CoroutineStart.LAZY。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()}")这样一来只会在调用了
start或者await方法之后,整个协程才会启动。 -
作用域异常
一旦作用域中抛出了异常,一般情况下都会直接导致所有兄弟协程中断,以及等待的父协程中断。
但是使用全局作用域的协程并不会有中断问题,即使其中一个子协程抛出异常,其他的照跑不误。
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() }打印出
Second child throws an exception First child was cancelled