join() 的功能是:讓目前的 Coroutine 暫停,直到指定的 Job 完成才繼續往下走。當有多個 Job 要一起等的時候,可以改用 joinAll()。
join 的基本用法#
fun main() = runBlocking {
val job = launch {
delay(100)
println("inner")
}
job.join()
println("outer")
}如果沒有 job.join(),外層會比 launch 內的程式先執行;加上 job.join() 後,外層必須等子 Coroutine 結束才會印出 outer。
joinAll:一次等多個 Job#
joinAll() 等價於對每個 Job 各自呼叫一次 join():
suspend fun joinAll(vararg jobs: Job)suspend fun Collection<Job>.joinAll()它提供兩種多載:可變參數版本,以及對 Collection<Job> 的擴充函式。
fun main() = runBlocking {
val jobs = mutableListOf<Job>()
repeat(10) {
jobs.add(launch {
delay(100)
println("inner: $it: ${Thread.currentThread().name}")
})
}
jobs.joinAll()
println("outer: ${Thread.currentThread().name}")
}外層會等到全部子 Coroutine 都結束之後才印出 outer。
其中一個 Job 被取消#
如果其中某些 Job 被取消,joinAll() 會等所有 Job「結束」(不論完成或取消)後才回到外層。下例用 withTimeout 限制每個子 Coroutine 的執行時間:
fun main() = runBlocking {
val jobs = mutableListOf<Job>()
repeat(10) {
jobs.add(launch {
withTimeout(500) {
delay(100L * it)
println("inner: $it: ${Thread.currentThread().name}")
}
})
}
jobs.joinAll()
println("outer: ${Thread.currentThread().name}")
}由於 delay 時間是 100L * it,從 it = 6 起會超過 500 毫秒、被 withTimeout 取消,因此只會看到 it = 0 到 5 的輸出。
其中一個 Job 拋例外#
當 launch 中拋出例外時,可以用 CoroutineExceptionHandler 接住未處理的例外:
class Day14 {
private val scope = CoroutineScope(Job() + CoroutineExceptionHandler { _, e -> println(e) })
suspend fun tryException() {
val jobs = mutableListOf<Job>()
repeat(10) {
jobs.add(scope.launch {
delay(100L)
yield()
println("inner: $it: ${Thread.currentThread().name}")
if (it == 2) {
throw RuntimeException("Incorrect")
}
})
}
jobs.joinAll()
println("outer: ${Thread.currentThread().name}")
}
}it == 2 的子 Coroutine 拋出 RuntimeException,由 CoroutineExceptionHandler 印出,其他子 Coroutine 仍會跑完,最後外層才印出 outer。
awaitAll:對應 async 的版本#
針對多個 Deferred,可以使用 awaitAll() 一次取得所有結果。它一樣提供可變參數與 Collection 兩種多載,回傳一個 List<T>。
fun main() = runBlocking {
val deferred = mutableListOf(
async {
delay(100)
println("inner: async1")
1
},
async {
delay(150)
println("inner: async2")
2
}
)
val result = deferred.awaitAll().sum()
println("outer: $result")
}要把多個 async 的結果合併,就看自己想怎麼處理;上例使用 sum() 把整數結果加總,輸出 outer: 3。
awaitAll 的例外處理方式與單一 await() 類似,使用 try-catch 包覆即可。
cancelAndJoin:cancel 後再 join#
實務上經常一起呼叫 cancel() 與 join():
job.cancel()
job.join()Kotlin Coroutine 提供了 cancelAndJoin() 把這兩步合併:
public suspend fun Job.cancelAndJoin() {
cancel()
return join()
}把上例改寫:
fun main() = runBlocking {
val job = launch {
println("inside: ${Thread.currentThread().name}")
withContext(Dispatchers.Default) {
delay(200)
println("Default: ${Thread.currentThread().name}")
}
}
delay(100)
job.cancelAndJoin()
println("outer: ${Thread.currentThread().name}")
}語意一樣,但程式更精簡。
原文出處#
- 原書/iThome:https://ithelp.ithome.com.tw/articles/10267485