kotlin coroutine文档:组合挂起函数

组合挂起函数

这部分讲述组合挂起函数的不同方法

默认是顺序的

假设我们有两个在别处定义的挂起函数,它们做一些有用的东西,像某种服务请求或者计算。我们假设它们仅仅是有用的,但是为了说明这个例子,每个实际上仅仅延迟一秒:

suspend fun doSomethingUsefulOne(): Int {
    delay(1000L) // 假设我们在这里做一些有用的事情
    return 13
}

suspend fun doSomethingUsefulTwo(): Int {
    delay(1000L) // 假设我们在这里也做一些有用的事情
    return 29
}

如果需要顺序调用它们,首先doSomethingUsefulOne,再是doSomethingUsefulTwo,然后计算它们的结果之和,我们应该怎么做?当我们需要用第一个函数的结果,决定我们是否需要调用或者决定怎么调用第二个函数,实际上我们需要怎么做。

我们需要一个正常的顺序调用,因为在协程中代码,就像在常规的代码中一样,默认是顺序的。如下例子通过计算执行两个挂起函数的总共实际来展示:

import kotlinx.coroutines.*
import kotlin.system.*

fun main() = runBlocking<Unit> {
//例子开始
    val time = measureTimeMillis {
        val one = doSomethingUsefulOne()
        val two = doSomethingUsefulTwo()
        println("The answer is ${one + two}")
    }
    println("Completed in $time ms")
//例子结束    
}

suspend fun doSomethingUsefulOne(): Int {
    delay(1000L) // 假设我们在这里做一些有用的事情
    return 13
}

suspend fun doSomethingUsefulTwo(): Int {
    delay(1000L) // 假设我们在这里也做一些有用的事情
    return 29
}

在这里获取完整代码

产生的结果如下:

The answer is 42
Completed in 2017 ms

使用async并行

如果调用doSomethingUsefulOne和doSomethingUsefulTwo两个之间没有任何依赖,我们想要更快地获取答案,并行地执行两者,该怎么办呢?这里async能帮上忙了。

概念上,async和launch很像。它开启了独立协程,它是一个轻量级线程,和其他协程一起并行地工作。区别在于,launch返回一个Job,没有带有任何结果值,然而async返回一个Deferred—一个轻量级非阻塞式future,它代表着后来要提供的一个promise。你可以在延缓值上用.await()来获取它的最后结果,但是deferred也是一个Job,所以如果需要你可以取消。[注: 如果你对于 future, promise, deferred 等概念感到困惑,可以先阅读并发 Promise 模型或其他资料了解相关概念。]

import kotlinx.coroutines.*
import kotlin.system.*

fun main() = 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")
//例子结束  
}

suspend fun doSomethingUsefulOne(): Int {
    delay(1000L) // 假设我们在这里做一些有用的事情
    return 13
}

suspend fun doSomethingUsefulTwo(): Int {
    delay(1000L) // 假设我们在这里也做一些有用的事情
    return 29
}

在这里获取完整代码

它产生像如下的结果:

The answer is 42
Completed in 1017 ms

这里快上两倍,因为我们有两个协程并行执行。注意,协程的并行总是显示的。

懒开启async

使用一个带有CoroutineStart.LAZY的可选start参数,async有一个懒选项。它仅仅当它的结果需要通过某个await或者调用start函数,才开启了协程。运行下面的例子:

import kotlinx.coroutines.*
import kotlin.system.*

fun main() = runBlocking<Unit> {
//例子开启
    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")
//例子结束 
}

suspend fun doSomethingUsefulOne(): Int {
    delay(1000L) // 假设我们在这里做一些有用的事情
    return 13
}

suspend fun doSomethingUsefulTwo(): Int {
    delay(1000L) // 假设我们在这里也做一些有用的事情
    return 29
}

在这里获取完整代码

它产生像如下的结果:

The answer is 42
Completed in 1017 ms

所以,这里定义了两个协程,但是没有像上次那个例子一样执行。但是控制权给了程序员,关于确切什么时候通过调用start开启了执行。我们首先开启one,然后开启two,然后等待各个协程结束。

注意,如果我们在println里面调用await,忽略对各个线程的start,然后我们将会得到顺序的行为,因为await开启了协程执行,而且等待执行的结束,这不是预期的懒用例。当值的计算包含了挂起函数,async(start = CoroutineStart.LAZY)这个用例是标准lazy函数的替代品。

Async样式的函数

我们可以定义Async样式的函数,通过带有显式GlobalScope 引用的async协程生成器,异步调用doSomethingUsefulOne和doSomethingUsefulTwo。我们用“Async”后缀来命名这个函数,强调这个事实:它们仅仅开启异步的计算,需要使用最终的延缓值来获取结果。

// somethingUsefulOneAsync结果类型是Deferred
fun somethingUsefulOneAsync() = GlobalScope.async {
    doSomethingUsefulOne()
}

// somethingUsefulTwoAsync结果类型是Deferred
fun somethingUsefulTwoAsync() = GlobalScope.async {
    doSomethingUsefulTwo()
}

注意,这些xxxAsync函数不是挂起函数。它们可以在任何地方使用。然而,它们的使用总是蕴含着用调用代码的它们行为异步(这里意味着并行)执行。

如下例子展示了在协程外部的使用:

import kotlinx.coroutines.*
import kotlin.system.*

//例子开始
// 注意,在这个例子中我们没有在`main`右边的`runBlocking` 
fun main() {
    val time = measureTimeMillis {
        // 我们可以在一个协程外面初始化async行为
        val one = somethingUsefulOneAsync()
        val two = somethingUsefulTwoAsync()
        // 但是等待结果必须包括要么是挂起的或者阻塞式。
        // 这里我们使用`runBlocking { ... }`来阻塞主线程,同时等待结果
        runBlocking {
            println("The answer is ${one.await() + two.await()}")
        }
    }
    println("Completed in $time ms")
}
//例子结束
fun somethingUsefulOneAsync() = GlobalScope.async {
    doSomethingUsefulOne()
}

fun somethingUsefulTwoAsync() = GlobalScope.async {
    doSomethingUsefulTwo()
}

suspend fun doSomethingUsefulOne(): Int {
    delay(1000L) // pretend we are doing something useful here
    return 13
}

suspend fun doSomethingUsefulTwo(): Int {
    delay(1000L) // pretend we are doing something useful here, too
    return 29
}

在这里获取完整代码

这个async函数程序样式在这里仅仅是为了说明,因为它在其他编程语言中时很受欢迎。Kotlin协程使用这个样式,是强烈不推荐的,理由下面会解释。

考虑下,在val one = somethingUsefulOneAsync() 这一行 和 one.await()表达式之间,在代码中有某个逻辑错误,程序抛出一个异常,这个程序执行的操作终止了。通常,一个全局的错误处理器会捕获这个异常,为了开发者记录和报告这个错误,但是另外这个程序继续其他的操作。但是这里somethingUsefulOneAsync仍旧在后台运行,虽然有这个事实,已经初始化的这个操作终止了。这个问题不会在结构化并行上不会发生,就像下一部分所展示。

Structured concurrency with async

让我们把Concurrent using async作为例子,抽取一个函数,并行地执行doSomethingUsefulOne和doSomethingUsefulTwo,然后返回它们结果之和。因为async协程生成器是在CoroutineScope上定义的一个扩展,我们需要把它包括在作用域里面,也就是coroutineScope函数提供的:

suspend fun concurrentSum(): Int = coroutineScope {
    val one = async { doSomethingUsefulOne() }
    val two = async { doSomethingUsefulTwo() }
     one.await() + two.await()
}

这是方式是,如果在concurrentSum代码里面发生错误了,它会抛出一个异常,所有这个作用域里面启动的协程都会被取消。

import kotlinx.coroutines.*
import kotlin.system.*

fun main() = runBlocking<Unit> {
//例子开始
    val time = measureTimeMillis {
        println("The answer is ${concurrentSum()}")
    }
    println("Completed in $time ms")
//例子结束    
}

suspend fun concurrentSum(): Int = coroutineScope {
    val one = async { doSomethingUsefulOne() }
    val two = async { doSomethingUsefulTwo() }
     one.await() + two.await()
}

suspend fun doSomethingUsefulOne(): Int {
    delay(1000L) // 假设我们在这里做一些有用的事情
    return 13
}

suspend fun doSomethingUsefulTwo(): Int {
    delay(1000L) // 假设我们在这里也做一些有用的事情
    return 29
}

在这里获取完整代码

从上面main函数的结果看来,我们仍旧并行地执行两个协程:

The answer is 42
Completed in 1017 ms

取消总是通过协程层级传播:

import kotlinx.coroutines.*

fun main() = runBlocking<Unit> {
    try {
        failedConcurrentSum()
    } catch(e: ArithmeticException) {
        println("Computation failed with ArithmeticException")
    }
}

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()
}

在这里获取完整代码

注意,子字协程失败时,第一个async和等待着的父协程也被取消了:

Second child throws an exception
First child was cancelled
Computation failed with ArithmeticException

你可能感兴趣的:(Kotlin,Coroutine,Android)