yield() 字面上的意思是「讓出」。在 Coroutine 中,它的作用是:如果可能,把目前 Coroutine 占用的執行緒(或執行緒池)讓給同一個 Dispatcher 上的其他 Coroutine 去執行。
在巢狀 Coroutine 中觀察 yield#
下面的範例由外層 launch 包住一個內層 launch,藉此看清楚 yield() 何時把執行權交出去。
fun main() = runBlocking {
val job = launch {
val child = launch {
try {
println("run child")
delay(Long.MAX_VALUE)
} finally {
println("Child is cancelled")
}
}
println("run parent")
yield()
println("Cancelling child")
child.cancel()
child.join()
yield()
println("Parent is not cancelled")
}
job.join()
}執行結果:
run parent
run child
Cancelling child
Child is cancelled
Parent is not cancelled順序解讀:
- 從外層 Coroutine 開始跑,先印出
run parent。 - 呼叫
yield()把執行權交給子 Coroutine,於是印出run child,子 Coroutine 接著進入delay(Long.MAX_VALUE)等待,執行權回到外層。 - 外層印出
Cancelling child,呼叫child.cancel()取消子 Coroutine,子 Coroutine 的finally印出Child is cancelled。 - 第二個
yield()因為已經沒有其他可執行的同層 Coroutine,沒有實際讓出的對象,外層直接印出Parent is not cancelled。
兩個 launch 交錯輸出#
把兩個 launch 都加入 yield(),可以觀察到兩個 Coroutine 交錯執行:
fun main() = runBlocking {
val job1 = launch {
repeat(10) {
println("coroutine1: $it")
yield()
}
}
val job2 = launch {
repeat(10) {
println("coroutine2: $it")
yield()
}
}
job1.join()
job2.join()
}每次 yield() 都把執行權交給對方,所以輸出會是 coroutine1: 0、coroutine2: 0、coroutine1: 1、coroutine2: 1 ⋯⋯ 一路交錯到 9。
被取消的 Coroutine 不會回來#
如果一個 Coroutine 已經被取消,即使它原本應該因為 yield() 被叫回來執行,也不會再恢復。
fun main() = runBlocking {
val job1 = launch {
repeat(10) {
println("coroutine1: $it")
yield()
}
}
val job2 = launch {
repeat(10) {
println("coroutine2: $it")
job1.cancel()
yield()
}
}
job1.join()
job2.join()
}job2 內呼叫了 job1.cancel(),之後即使呼叫 yield(),也沒有 job1 可以切換回去,因此最終只會看到 coroutine1: 0 一次,後面全部都是 coroutine2 的輸出。
yield 與 delay 的差異#
兩者表面上看起來都讓 Coroutine 暫停,實際上意義不同:
delay():把目前 Coroutine 設為等待,等指定的時間到才恢復。yield():放棄當下的執行權,讓同一個 Dispatcher 上其他可執行的 Coroutine 接手;如果沒有其他人可接手,就直接繼續往下執行。
原文出處#
- 原書/iThome:https://ithelp.ithome.com.tw/articles/10266141