首发于公众号: 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`,其他是一模一样。进去一看
```js
/**
* 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
Kotlin学习笔记之 13 基础操作符run、with、let、also、apply