當希望某段 Coroutine 程式必須在限定時間內完成,可以使用 withTimeout 或 withTimeoutOrNull。
suspend fun <T> withTimeout(
timeMillis: Long,
block: suspend CoroutineScope.() -> T
): T兩個參數:時間(毫秒),以及要執行的 Coroutine 區塊。
withTimeout 基本用法#
fun main() = runBlocking {
val job = launch {
println("inside: ${Thread.currentThread().name}")
withTimeout(200) {
repeat(10) {
println("delay $it times")
delay(50)
yield()
}
println("withTimeout")
}
}
delay(100)
println("outer: ${Thread.currentThread().name}")
}repeat(10) 內每次 delay(50),跑十次需要 500 毫秒;用 withTimeout(200) 包起來後,超過 200 毫秒會把區塊取消。
執行結果:
inside: main
delay 0 times
delay 1 times
outer: main
delay 2 times
delay 3 timesrepeat 區塊只跑了四次就被取消,符合 200 毫秒的限制。
TimeoutCancellationException#
withTimeout 在時間到時其實會丟出 TimeoutCancellationException。因為這個例外是 CancellationException 的子類別,預設會被 Coroutine 框架靜靜處理掉;如果要自行處理,可以在外層用 try-catch 攔截:
fun main() = runBlocking {
val job = launch {
println("inside: ${Thread.currentThread().name}")
try {
withTimeout(200) {
repeat(10) {
println("delay $it times")
delay(50)
yield()
}
println("withTimeout")
}
} catch (e: TimeoutCancellationException) {
println(e.message)
}
}
delay(100)
println("outer: ${Thread.currentThread().name}")
}最後會多一行 Timed out waiting for 200 ms。
withTimeout 可取消#
withTimeout 與 withContext 一樣,會跟著外層 Coroutine 被取消:
fun main() = runBlocking {
val job = launch {
println("inside: ${Thread.currentThread().name}")
try {
withTimeout(200) {
repeat(10) {
println("delay $it times")
delay(50)
yield()
}
println("withTimeout")
}
} catch (e: TimeoutCancellationException) {
println(e.message)
}
}
delay(100)
job.cancel()
job.join()
println("outer: ${Thread.currentThread().name}")
}外層 100 毫秒後呼叫 job.cancel(),連帶把 withTimeout 區塊一起取消,repeat 第三次之後就不會再執行。
withTimeoutOrNull#
withTimeoutOrNull 行為和 withTimeout 幾乎一樣,差別在於:
- withTimeout 在超時會丟出
TimeoutCancellationException。 - withTimeoutOrNull 在超時會回傳
null;如果在時間內順利執行完,會回傳區塊最後的值。
fun main() = runBlocking {
val job = launch {
println("inside: ${Thread.currentThread().name}")
val timeout = withTimeoutOrNull(600) {
repeat(10) {
println("delay $it times")
delay(50)
yield()
}
return@withTimeoutOrNull 10
}
println("timeout= $timeout")
}
delay(100)
println("outer: ${Thread.currentThread().name}")
}當 timeout 設為 600 毫秒,足以跑完 10 次,最後印出 timeout= 10。
把 timeout 改成 200 毫秒:
val timeout = withTimeoutOrNull(200) {
repeat(10) {
println("delay $it times")
delay(50)
yield()
}
return@withTimeoutOrNull 10
}
println("timeout= $timeout")repeat 跑到第四次就被中斷,最後 withTimeoutOrNull 回傳 null,輸出 timeout= null。
小結#
- 想要限制 Coroutine 區塊的執行時間,用
withTimeout或withTimeoutOrNull。 - 超時時:withTimeout 丟出
TimeoutCancellationException,可用try-catch攔截;withTimeoutOrNull 回傳null。 - 區塊有回傳值時,
withTimeoutOrNull用起來會比較順手。
原文出處#
- 原書/iThome:https://ithelp.ithome.com.tw/articles/10267189