Kotlin学习笔记之 31 协程挂起函数的组合

341 阅读2分钟

首发于公众号: 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 &mdash; 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