要讓 suspend 函式真的跑起來,必須把它放進一個 Coroutine 範圍內執行。Kotlin Coroutine 提供了兩個常用的 Coroutine Builder:launch 與 async。本章先聚焦在沒有回傳值的場景:launch。

launch 的基本用法#

當一段非同步任務不需要回傳結果(也就是回傳 Unit),就適合用 launch 建立 Coroutine。

fun main() = runBlocking {
    launch {
        println("launch Start")
        delay(500)
        println("launch End")
    }
    println("Done")
}

執行這段程式時,會先看到 Done,再看到 launch 區塊內的兩段輸出。原因是 launch 會建立一個新的 Coroutine,成為 runBlocking 的子 Coroutine;外層的 println("Done") 會先被執行,內層的 launch 任務之後才接著被排程。

把 launch 換成 coroutineScope { ... },輸出順序就會回到原始碼的順序。coroutineScope 不會建立新的 Coroutine,而是繼承外層的 Coroutine context,所以裡外其實是同一個 Coroutine。

把區塊抽成 suspend 函式#

launch 區塊裡若包含 delay 這類 suspend 函式,把整段抽出去時,函式必須以 suspend 修飾:

suspend fun launchFun() {
    println("launch Start")
    delay(500)
    println("launch End")
}

suspend 函式只能在 CoroutineScope 中、或被另一個 suspend 函式呼叫。

用 Job 控制 Coroutine#

launch 的回傳值是 Job。Job 代表一個可以被取消的任務,呼叫 cancel() 就能停止這個 Coroutine。

cancel:主動取消任務#

fun main() = runBlocking {
    val job = launch {
        repeat(100_000) { index ->
            println("launch Start $index")
            delay(500)
            println("launch End $index")
        }
    }
    delay(1100)
    job.cancel()
    println("Done")
}

外層 Coroutine 在 delay(1100) 後呼叫 job.cancel(),把原本要重複十萬次的任務中止,外層才繼續執行剩下的程式。

join:等待子 Coroutine 完成#

預設情況下,外層的程式會比 launch 區塊先執行。如果想反過來,先讓 launch 內的工作完成、再繼續往下走,可以使用 job.join()

fun main() = runBlocking {
    val job = launch {
        println("launch Start")
        delay(500)
        println("launch End")
    }
    job.join()
    println("Done")
}

joinAll:一次等待多個 Job#

針對多個 Job 同時做 join(),可以使用 joinAll

fun main() = runBlocking {
    val job = launch {
        println("launch Start")
        delay(500)
        println("launch End")
    }
    joinAll(job)
    println("Done")
}

joinAll 內部其實就是把每個 Job 都呼叫 join()

public suspend fun joinAll(vararg jobs: Job): Unit = jobs.forEach { it.join() }

延遲啟動:CoroutineStart.LAZY#

launch 預設一執行就會被排程啟動。如果想延後啟動,可以在 launch 時帶入 CoroutineStart.LAZY

fun main() = runBlocking {
    launch(start = CoroutineStart.LAZY) {
        println("launch Start")
        delay(500)
        println("launch End")
    }
    println("Done")
}

加上 CoroutineStart.LAZY 之後,這個 launch 不會立刻執行;如果一直沒有人去啟動它,整段程式會卡在等待。

要主動啟動,可以呼叫 job.start(),或用 job.join() 在等待時順帶啟動:

fun main() = runBlocking {
    val job = launch(start = CoroutineStart.LAZY) {
        println("launch Start")
        delay(500)
        println("launch End")
    }
    job.start()
    println("Done")
}

小結#

  • launch 用來建立沒有回傳值的 Coroutine,回傳 Job。
  • Job 提供 cancel()start()join() 來掌控 Coroutine 的生命週期。
  • 預設 launch 一被呼叫就會排程執行,若需要延後啟動,可帶入 CoroutineStart.LAZY,再透過 start()join() 觸發。

原文出處#

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