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 2017 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.")
}
}
讀者可以自己試一下上面的代碼。
總結
以上是生活随笔為你收集整理的fun(n) Java_java程序员的kotlin课(N+2):suspending函数执行编排的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: leetcode机器人运动范围Java_
- 下一篇: excel趋势线公式导出_Java 添加