3、suspend函数的使用(组合)

suspend函数的使用(官方文档)
这部分讨论suspend函数的各种使用方式。

目录

1、默认顺序执行

2、使用异步执行(async)

3、延迟执行异步

4、异步风格的函数(Async-style )

5、结构化并发


1、默认顺序执行

假如有两个suspend函数,他们需要去访问远程服务或者执行一些计算,对于当前的例子我们使用延时来模仿实际的任务,代码如下:

package com.cool.cleaner.test

import kotlinx.coroutines.delay

suspend fun doSomethingUsefulOne(): Int {
    delay(1000)
    return 13
}

suspend fun doSomethingUsefulTwo(): Int {
    delay(1000)
    return 29
}

假如需要顺序的调用他们,首先是doSomethingUsefulOne 然后是doSomethingUsefulTwo,最后把他们的结果相加,那该怎么做呢?实际中我们首先调用第一个函数然后根据返回的结果决定我们是否需要调用第二个函数或者怎么调用第二个函数。
我们可以使用正常的顺序调用,因为在协程中就行常规的代码一样默认也是顺序执行的;下面的代码计算两个suspend函数的执行时间之和,也证实了协程中默认是顺序调用的:

package com.cool.cleaner.test

import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
import kotlin.system.measureTimeMillis

suspend fun doSomethingUsefulOne(): Int {
    delay(1000)
    return 13
}

suspend fun doSomethingUsefulTwo(): Int {
    delay(1000)
    return 29
}

fun main() = runBlocking {
    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 2032 ms

Process finished with exit code 0

2、使用异步执行(async)

假如doSomethingUsefulOne 和doSomethingUsefulTwo 两个之间没有关联关系,而且我们希望能并发的执行他们以便更快的得到结果,那该怎么做呢?这就是async的用武之地了。
概念上来说async就像launch一样,它会启动一个轻量级的线程(也就是协程)并且能与其他的协程一起并发的执行。不同的是launch返回一个job,但是这个job并不会携带有返回值,而async返回一个Deferred对象(a light-weight non-blocking future),而你可以使用这个Deferred对象去获取返回值。你可以使用Deferred对象的await方法获取最终的返回值,由于Deferred也是一个Job,因此如果你有需要的话你也可以用他来执行取消操作,代码如下:

package com.cool.cleaner.test

import kotlinx.coroutines.async
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
import kotlin.system.measureTimeMillis

suspend fun doSomethingUsefulOne(): Int {
    delay(1000)
    return 13
}

suspend fun doSomethingUsefulTwo(): Int {
    delay(1000)
    return 29
}

fun main() = runBlocking {
    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 1073 ms

Process finished with exit code 0

这比上面快了两倍,因为两个协程都是并发执行的;请注意协程的并发执行都是显示的(就是说你要手动调用async才会并发执行)。

3、延迟执行异步

async的执行也是可以延时的,你可以把它的参数start指定为CoroutineStart.LAZY以达到延时的效果;这个模式下,只有你调用了await获取结果或者调用了Job.start函数它才会开始执行相应的协程,你可以运行下面的例子看看:

package com.cool.cleaner.test

import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.async
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
import kotlin.system.measureTimeMillis

suspend fun doSomethingUsefulOne(): Int {
    delay(1000)
    return 13
}

suspend fun doSomethingUsefulTwo(): Int {
    delay(1000)
    return 29
}

fun main() = runBlocking {
    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 1062 ms

Process finished with exit code 0

这里定义了两个协程但是他们并不是像上面的例子一样立即执行,现在我们可以在需要执行的时候调用它的start方法;这里首先开始one,然后是two,然后等待他们每一个执行结束。
注意,如果我们只是调用了await而在此之前没有调用start启动协程那结果就是顺序执行的,因为await会启动协程然后等待它执行完毕,这种方式并不是延迟执行的惯用法;async(start = CoroutineStart.LAZY)这种用法是标准库中lazy函数的一种suspend版本。

4、异步风格的函数(Async-style )

我们可以使用GlobalScope的async协程构建器定义异步风格的函数,然后调用doSomethingUsefulOne 和doSomethingUsefulTwo,我们在函数名后面加了"...Async"表示函数只是启动了协程执行任务并返回一个Deferred对象,如果你需要任务执行结果的话你需要使用Deferred对象来获取,代码如下:

suspend fun doSomethingUsefulOne(): Int {
    delay(1000)
    return 13
}

suspend fun doSomethingUsefulTwo(): Int {
    delay(1000)
    return 29
}
//return type is Deferred
fun someThingUsefulOneAsync() = GlobalScope.async {
    doSomethingUsefulOne()
}
fun doSomethingUsefulTwoAsync() = GlobalScope.async {
    doSomethingUsefulTwo()
}

请注意,这里的xxxAsync函数并不是suspend函数,所以可以在任何地方使用,然后使用他们的话通常意味着是异步执行(并发执行)的。如何使用他们呢?请看下面的代码:

package com.cool.cleaner.test

import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.async
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
import kotlin.system.measureTimeMillis

suspend fun doSomethingUsefulOne(): Int {
    delay(1000)
    return 13
}

suspend fun doSomethingUsefulTwo(): Int {
    delay(1000)
    return 29
}
//return type is Deferred
fun someThingUsefulOneAsync() = GlobalScope.async {
    doSomethingUsefulOne()
}
fun doSomethingUsefulTwoAsync() = GlobalScope.async {
    doSomethingUsefulTwo()
}
fun main() {
    val time = measureTimeMillis {
        val one = someThingUsefulOneAsync()
        val two = doSomethingUsefulTwoAsync()
        runBlocking {//阻塞主线程获取执行结果
            println("The answer is ${one.await() + two.await()}")
        }
    }
    println("Completed in $time ms")
}

请注意,这里使用的异步函数(async function)编程风格仅仅是为了说明情况,因为这在其他语言中是一种流行的编程风格;但是在kotlin中这种方式是极不推荐的,下面会解释原因。

试想一下如果在val one = somethingUsefulOneAsync() 这行以及 one.await()之间有一些逻辑错误然后抛出了异常,此时程序的任务也终止了;正常情况下一个全局的异常捕获器将会捕获这个异常并把异常报告给开发者,此时程序应该去做其任务,但是这里的somethingUsefulOneAsync函数却还在后台继续执行即使他的执行结果已经不需要了;此时我们可以使用结构化并发,结构化并发并不存在这里说的问题。

5、结构化并发

这里定义一个函数并让它异步的计算doSomethingUsefulOne 和doSomethingUsefulTwo,然后返回他们的结果之和,由于async构建器定义为CoroutineScope的一个扩展,因此我们需要把async放在CoroutineScope之中,这正是coroutineScope函数提供的功能,代码如下:

suspend fun doSomethingUsefulOne(): Int {
    delay(1000)
    return 13
}

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

以这种方式来看如果在函数concurrentSum内部出现了问题并抛出了异常,那么在concurrentSum的协程范围(由coroutineScope 创建)内启动的协程都将会自动取消,代码如下:

package com.cool.cleaner.test

import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
import kotlin.system.measureTimeMillis

suspend fun doSomethingUsefulOne(): Int {
    delay(1000)
    return 13
}

suspend fun doSomethingUsefulTwo(): Int {
    delay(1000)
    return 29
}
suspend fun concurrentSum(): Int = coroutineScope {
    val one = async { doSomethingUsefulOne() }
    val two = async { doSomethingUsefulTwo() }
    one.await() + two.await()
}
fun main() = runBlocking {
    val time = measureTimeMillis { println("The answer is ${concurrentSum()}") }
    println("Completed in $time ms")
}

我们的任务还是并发的执行,可以从下面的输出看出来:

The answer is 42
Completed in 1077 ms

Process finished with exit code 0

协程的取消会在父子协程之间传播,如下所示:

package com.cool.cleaner.test

import kotlinx.coroutines.async
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking

suspend fun failedConcurrentSum(): Int = coroutineScope {//父协程
    val one = async {//子协程1
        try {
            delay(Long.MAX_VALUE)
            42
        } finally {
            println("First child was cancelled")
        }
    }
    val two = async {//子协程2
        println("Second child throws an exception")
        throw ArithmeticException()
    }
    one.await() + two.await()
}
fun main() = runBlocking {
    try {
        failedConcurrentSum()
    } catch (e: java.lang.ArithmeticException) {
        println("Computation failed with ArithmeticException")
    }
}

请注意,由于two这个协程的取消导致one以及父协程的取消并最终在main函数中得以捕获,执行结果如下:

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

Process finished with exit code 0

 

你可能感兴趣的:(Kotlin协程,kotlin,协程,并发,coroutine)