100字范文,内容丰富有趣,生活中的好帮手!
100字范文 > fun(n) Java_java程序员的kotlin课(N+2):suspending函数执行编排

fun(n) Java_java程序员的kotlin课(N+2):suspending函数执行编排

时间:2024-01-06 01:09:46

相关推荐

fun(n) Java_java程序员的kotlin课(N+2):suspending函数执行编排

执行编排指的是对异步函数的执行顺序进行控制,举个简单的例子:

需要调用a、b两个函数,从高效的角度讲a、b并行调用有一定是最佳的,但是如果b函数的输入依赖a函数的输出,那就只能做串行调用。

针对a、b两个函数执行顺序的控制,即本节要讲的执行编排,原文中的 Composing Suspending Functions

默认串行执行

假设我们有两个suspending的函数,可能是远程调用(rpc)。我们假装这两个函数很有用,虽然这两个函数只是简单的delay了一下.

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

}

我们如何让这两个函数串行的执行呢?依次执行doSomethingUsefulOne和doSomethingUsefulTwo,然后计算两个函数结果的和,请看下面的代码(为了计算两个函数总的执行时间,此处利用了measureTimeMillis函数)

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 ms

并行执行异步操作

如果两次函数调用之间没有有起来关系,我们就可以并行的执行两个函数了,这样执行起来总的耗时会减少。

从概念上讲,async和launch是相似的。他们都可以启动一个协程,区别在于launch返回一个job对象,job对象不携带任何结果相关的信息。而async返回一个Deferred类型的对象,和future类似,不过这个是在协程背景下的,可以通过.await()函数来获得结果。而且Deferred也是一个job,一样可以用来cancel协程。

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 1017 ms

执行速度有两倍的提升,因为两个协程是并行执行的。谨记:利用协程的并行永远是显式的。

延迟启动异步动作

作为一个可选项,在使用async关键字启动协程的时候,可以传入参数CoroutineStart.LAZY。在这种模式下,协程启动的时机:

await函数被调用时

start函数被调用时

val time = measureTimeMillis {

val one = async(start = CoroutineStart.LAZY) { doSomethingUsefulOne() }

val two = async(start = CoroutineStart.LAZY) { doSomethingUsefulTwo() }

// some computation

one.start() // start the first one

two.start() // start the second one

println("The answer is ${one.await() + two.await()}")

}

println("Completed in $time ms")

结果如下:

The answer is 42

Completed in 1017 ms

上面的例子中,两个协程并没有像之前的例子一样立刻启动,启动的控制权交给了编码者,编码者可以通过start函数或者await函数进行启动。

但是需要注意的是,如果使用await来触发协程的启动,上面的例子会变为串行执行,因为aswit函数会等待协程执行完毕才向下执行接下来的动作。

异步风格的函数

我们可以通过 async coroutine builder 和显式的 GlobalScope来把 doSomethingUsefulOne函数和doSomethingUsefulTwo函数声名为异步函数:

// The result type of somethingUsefulOneAsync is Deferred

fun somethingUsefulOneAsync() = GlobalScope.async {

doSomethingUsefulOne()

}

// The result type of somethingUsefulTwoAsync is Deferred

fun somethingUsefulTwoAsync() = GlobalScope.async {

doSomethingUsefulTwo()

}

请注意,上面的xxxAsync函数并不是suspending函数,他们可以在任何地方被调用。下面的代码是用来调用上面两个函数的代码:

// note that we don't have `runBlocking` to the right of `main` in this example

fun main() {

val time = measureTimeMillis {

// we can initiate async actions outside of a coroutine

val one = somethingUsefulOneAsync()

val two = somethingUsefulTwoAsync()

// but waiting for a result must involve either suspending or blocking.

// here we use `runBlocking { ... }` to block the main thread while waiting for the result

runBlocking {

println("The answer is ${one.await() + two.await()}")

}

}

println("Completed in $time ms")

}

上面这种编码风格仅用来展示,因为在其他语言中,这是一种很流行的编码方式,但是在kotlin中这种编码风格是强烈不推荐的。

还记得前几章有讲过一个词结构化并发或者英文原版structured concurrency,啥意思呢,就是当父协程取消、异常、关闭时,需要保证子协程都有被正确的取消,避免资源泄漏。

那么上面的代码呢?如果val one = somethingUsefulOneAsync()这一行到one.await()这一行发生任何错误,会怎样?主协程停止了,报错了,但是子协程会一直执行。

通过async做结构化异步编程

所以上面的例子,还是需要进行结构化的编码风格来做:

fun main() = runBlocking {

var a = async { doSomethingUsefulOne() }

var b = async { doSomethingUsefulTwo() }

println(a.await() + b.await())

}

suspend fun doSomethingUsefulOne(): Int {

delay(1024)

return 23

}

suspend fun doSomethingUsefulTwo(): Int {

delay(800)

return 35

}

这种编码风格,如果main中发生任何错误,所有的子协程都将被取消。

fun main() = runBlocking {

var a = async { doSomethingUsefulOne() }

var b = async { doSomethingUsefulTwo() }

println(a.await() + b.await())

}

suspend fun doSomethingUsefulOne(): Int {

delay(200)

throw RuntimeException()

return 23

}

suspend fun doSomethingUsefulTwo(): Int {

try {

//永远都不会被执行

delay(Long.MAX_VALUE)

return 35

} finally {

println("cancel.")

}

}

读者可以自己试一下上面的代码。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。