Coroutine 有三大要素:CoroutineScope、suspend 函式、Dispatcher。其中 suspend 函式負責處理非同步任務,當 Coroutine 執行到 suspend 函式時,這個 Coroutine 會被掛起(suspend),等任務完成之後再恢復執行。

Kotlin Coroutine 提供了一些內建的 suspend 函式,本章先看最常見的一個:delay。

為什麼不用 Thread.sleep#

過去在執行緒(Thread)上想暫停一下,會使用 Thread.sleep()。問題是 sleep 會阻塞整個執行緒,這段期間執行緒什麼事都做不了。

對應到 Coroutine 的情境,如果在 Coroutine 內呼叫 Thread.sleep(),就會把底層執行緒卡住,連同其他要在這個 Dispatcher 上跑的 Coroutine 都會被一起拖住。

fun main() = runBlocking {
    launch {
        Thread.sleep(100L)
        println("Coroutine 1 ${Thread.currentThread().name}")
    }

    launch {
        println("Coroutine 2 ${Thread.currentThread().name}")
    }
}

第一個 launch 在 Thread.sleep(100L) 期間把執行緒占住,所以第二個 launch 沒辦法接手;輸出順序會是 Coroutine 1 先、Coroutine 2 後。

IDE 會在 Coroutine 中使用 Thread.sleep() 的位置顯示 Inappropriate blocking method call 警告,提醒你不要在 Coroutine 裡呼叫會阻塞執行緒的方法。

改用 delay#

delay() 不會阻塞執行緒,它只是把目前這個 Coroutine 切到等待狀態。執行緒在等待期間可以去執行同一個 Dispatcher 上的其他 Coroutine。

fun main() = runBlocking {
    launch {
        delay(100L)
        println("Coroutine 1 ${Thread.currentThread().name}")
    }

    launch {
        println("Coroutine 2 ${Thread.currentThread().name}")
    }
}

第一個 launch 呼叫 delay() 後,這個 Coroutine 進入等待,執行緒被釋放出來給第二個 launch;等到 100 毫秒結束,第一個 Coroutine 才從暫停的位置繼續往下執行。最後印出的順序會是 Coroutine 2 先、Coroutine 1 後。

小結#

  • Thread.sleep() 會阻塞執行緒;delay() 只暫停目前的 Coroutine,執行緒仍可以服務其他 Coroutine。
  • 在 Coroutine 內想做「等一下」這件事,請用 delay(),不要用 Thread.sleep()

原文出處#

  • 原書/iThome:https://ithelp.ithome.com.tw/articles/10266141