launch 適合處理沒有回傳值的非同步任務,但實務上我們也常常需要從非同步任務取回結果,這時就輪到 async 上場。async 同樣會建立一個新的 Coroutine,差別在於它的回傳型別是 Deferred,而 Deferred 是會帶結果的 Job。

async 與 await#

async 用來建立會回傳值的 Coroutine。透過 await() 等待結果,回傳值就是區塊裡的最後一行(或 return@async 指定的值)。

fun main() = runBlocking {
    val deferred = async {
        println("async start")
        delay(500)
        println("async end")
        50
    }
    val deferredValue = deferred.await()
    println("done $deferredValue")
}

把 async 的回傳值存進 deferred。當外層程式呼叫 await() 時,會在這裡等到 async 區塊跑完,再把結果傳出去。

Kotlin 的 lambda 最後一行就是它的回傳值,可以省略 return。如果想顯式宣告,也可以寫成 return@async 50。Kotlin 會自動推斷回傳型別,IDE 通常會建議移除手動加上的型別宣告。

await 與 join 的差異#

await()join() 都會等到對應的 Coroutine 完成才繼續往下走,差別在於:

  • join() 沒有回傳值,只等任務結束。
  • await() 等任務結束後會回傳 async 區塊的值(如上例的 50)。

兩者都會在「Coroutine 完成或拋出例外」時結束等待。

沒有呼叫 await 會怎樣#

async 與 launch 一樣,建立後就會被立刻排程執行,並不會因為沒人呼叫 await() 就停在那裡。少了 await(),只是不會去取結果而已:

fun main() = runBlocking {
    async {
        println("async start")
        delay(500)
        println("async end")
        return@async 50
    }
    println("done")
}

執行順序就跟用 launch 寫一樣,外層的 done 會先被印出。

延遲啟動:CoroutineStart.LAZY#

跟 launch 相同,async 也支援用 CoroutineStart.LAZY 把 Coroutine 設成延遲啟動。差別是 async 比 launch 多了一個啟動方式 await(),因為 async 本來就會回傳值:

  • await():啟動並等待結果。
  • start():只啟動,不取結果。
  • join():啟動並等待完成,但不取結果。

其中只有 await() 會帶回 Deferred 的回傳值。

launch 與 async 的對照#

Builder回傳型別適用場景取得結果的方式
launchJob沒有回傳值的非同步任務不適用
asyncDeferred有回傳值的非同步任務await()

兩者本質上都是可被取消的背景任務。Job 與 Deferred 共有的方法包含 cancel()start()join();Deferred 額外多了 await() 用來取得結果。

小結#

  • async 用來處理「需要回傳值」的非同步任務,回傳的是 Deferred。
  • await() 會等待 async 完成並把結果傳出來;如果不需要結果,async 也會像 launch 一樣被排程執行。
  • 加上 CoroutineStart.LAZY 之後,async 同樣可以延後啟動,並用 await()start()join() 喚醒。

原文出處#

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