Android Kotlin 協程async
measureTimeMillis 統計一段程式碼耗時
內斂函式 measureTimeMillis{ } 可以很方便的統計一段程式碼執行的耗時。
使用:
GlobalScope.launch {
val time = measureTimeMillis {
delay(1000)
Log.d("zyj-", "日誌")
}
Log.d("zyj-", "耗時:$time")
}
輸出結果:
D/zyj-: 日誌
D/zyj-: 耗時:1010
使用預設順序
定義兩個耗時函式:
suspend fun doSomethingUsefulOne(): Int { delay(1000L) // 假設我們在這裡做了一些有用的事 return 13 } suspend fun doSomethingUsefulTwo(): Int { delay(1000L) // 假設我們在這裡也做了一些有用的事 return 29 }
使用預設的順序呼叫:
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
從輸出結果上看,兩個耗時任務是序列的,總耗時= 耗時函式1 + 耗時函式2
使用 async 併發
如果 doSomethingUsefulOne 與 doSomethingUsefulTwo 之間沒有依賴,並且我們想更快的得到結果,讓它們進行 併發 嗎?這就是async 可以幫助我們的地方。
在概念上,async 就類似於 launch。它啟動了一個單獨的協程與其它所有的協程一起併發的工作。不同之處在於 launch 返回一個 Job 並且不附帶任何結果值,而 async 返回一個 Deferred—— 一個非阻塞 future, 這代表了一個將會在稍後提供結果的 promise。你可以使用 .await()在一個延期的值上得到它的最終結果, 但是 Deferred 也是一個 Job,所以如果需要的話,你可以取消它。
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
可選的,async 可以通過將 start 引數設定為 CoroutineStart.LAZY 而變為惰性的。 在這個模式下,只有結果通過 await 獲取的時候協程才會啟動,或者在 Job 的 start函式呼叫的時候。執行下面的示例:
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 1017 ms
因此,在先前的例子中這裡定義的兩個協程沒有執行,但是控制權在於程式設計師準確的在開始執行時呼叫 start。我們首先 呼叫 one,然後呼叫 two,接下來等待這個協程執行完畢。
注意,如果我們只是在 println 中呼叫 await,而沒有在單獨的協程中呼叫 start,這將會導致順序行為,直到 await 啟動該協程 執行並等待至它結束,這並不是惰性的預期用例。 在計算一個值涉及掛起函式時,這個 async(start = CoroutineStart.LAZY)的用例用於替代標準庫中的 lazy 函式。
構建async 風格的函式
我們可以定義非同步風格的函式來 非同步 的呼叫 doSomethingUsefulOne 和 doSomethingUsefulTwo 並使用 async 協程建造器並帶有一個顯式的 GlobalScope 引用。 我們給這樣的函式的名稱中加上“……Async”字尾來突出表明:事實上,它們只做非同步計算並且需要使用延期的值來獲得結果。
// somethingUsefulOneAsync 函式的返回值型別是 Deferred<Int>
fun somethingUsefulOneAsync() = GlobalScope.async {
doSomethingUsefulOne()
}
// somethingUsefulTwoAsync 函式的返回值型別是 Deferred<Int>
fun somethingUsefulTwoAsync() = GlobalScope.async {
doSomethingUsefulTwo()
}
使用
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 我們可以在協程外面啟動非同步執行
val one = somethingUsefulOneAsync()
val two = somethingUsefulTwoAsync()
// 但是等待結果必須呼叫其它的掛起或者阻塞
// 當我們等待結果的時候,這裡我們使用 `GlobalScope.launch{ }` 來阻塞主執行緒
GlobalScope.launch {
println("The- answer is ${one.await()} ${two.await()}")
}
}
}
這種帶有非同步函式的程式設計風格僅供參考,因為這在其它程式語言中是一種受歡迎的風格。在 Kotlin 的協程中使用這種風格是強烈不推薦的, 原因如下所述。
考慮一下如果 val one = somethingUsefulOneAsync() 這一行和 one.await() 表示式這裡在程式碼中有邏輯錯誤, 並且程式丟擲了異常以及程式在操作的過程中中止,將會發生什麼。 通常情況下,一個全域性的異常處理者會捕獲這個異常,將異常列印成日記並報告給開發者,但是反之該程式將會繼續執行其它操作。但是這裡我們的 somethingUsefulOneAsync仍然在後臺執行, 儘管如此,啟動它的那次操作也會被終止。這個程式將不會進行結構化併發,如下一小節所示。
使用 async 的結構化併發
讓我們使用使用 async 的併發這一小節的例子並且提取出一個函式併發的呼叫 doSomethingUsefulOne 與 doSomethingUsefulTwo 並且返回它們兩個的結果之和。 由於 async 被定義為了 CoroutineScope上的擴充套件,我們需要將它寫在作用域內,並且這是 coroutineScope 函式所提供的:
suspend fun concurrentSum(): Int = coroutineScope {
val one = async { doSomethingUsefulOne() }
val two = async { doSomethingUsefulTwo() }
one.await() + two.await()
}
這種情況下,如果在 concurrentSum 函式內部發生了錯誤,並且它丟擲了一個異常, 所有在作用域中啟動的協程都會被取消。
val time = measureTimeMillis {
println("The answer is ${concurrentSum()}")
}
println("Completed in $time ms")
從上面的 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()
}
請注意,如果其中一個子協程(即 two)失敗,第一個 async 以及等待中的父協程都會被取消:
Second child throws an exception
First child was cancelled
Computation failed with ArithmeticException