launch vs async
可以透過下列任一方式啟動 coroutines:
launch
會啟動新的 coroutine,但不會傳回結果給呼叫端。任何視為「射後不理」的工作都可以使用launch
啟動。async
會啟動新的 coroutine,並透過await
這個 suspend function 回傳結果。
筆記:
- 通常從一般函式去
launch
一個新的 coroutine。 async
只能使用在 coroutine 裡面,或在 suspend function 裡面執行 parallel decomposition 時。- 一般函式無法呼叫
await
。
注意:
launch
和async
處理 exception 的方式不同。由於async
預期最終會呼叫await
,所以它會先將 exception 保留,並在呼叫await
時 throw exception。這意味著,如果使用async
,從一般函式啟動新的 coroutine,則可能會自動捨棄 exception。這些遭捨棄的 exception 不會顯示在 crash metrics,也不會記錄在 logcat。詳情可以參考下面這篇文章。
Parallel decomposition
所有在 suspend
function 裡啟動的 coroutine,都必須在函式傳回時停止,因此可能需要確保,這些 coroutine 要在 function 傳回前執行完畢。我們可以運用 coroutine「structured concurrency」的特性,定義 coroutineScope
來啟動一個或多個 coroutine。然後,使用 await()
(針對一個 coroutine) 或 awaitAll()
(針對多個 coroutine),就可確保這些 coroutine 在從函式傳回之前執行完畢。
例如,這邊定義一個 coroutineScope
以非同步方式來擷取兩份文件。對每個 deferred reference 呼叫 await()
,就能確保兩項 async
作業都會在 function 傳回之前執行完畢:
suspend fun fetchTwoDocs() =
coroutineScope {
val deferredOne = async { fetchDoc(1) }
val deferredTwo = async { fetchDoc(2) }
deferredOne.await()
deferredTwo.await()
}
另外也可以在 collections 上使用 awaitAll()
,如下所示:
suspend fun fetchTwoDocs() = // called on any Dispatcher (any thread, possibly Main)
coroutineScope {
val deferreds = listOf( // fetch two docs at the same time
async { fetchDoc(1) }, // async returns a result for the first doc
async { fetchDoc(2) } // async returns a result for the second doc
)
deferreds.awaitAll() // use awaitAll to wait for both network requests
}
使用 awaitAll()
會讓 fetchTwoDocs()
函式等待 async
啟動的 coroutines 執行完畢,然後才會回傳。不過要注意,如果沒有呼叫 awaitAll()
,coroutineScope
builder 不會繼續執行呼叫 fetchTwoDocs
的 coroutine,直到所有使用 async
啟動的 coroutine 執行完畢為止。
此外,coroutineScope
會 catch 所有 coroutine 丟出的 exceptions,並轉送至呼叫端。
如要進一步瞭解 parallel decomposition,可參考下面這篇文章。
https://kotlinlang.org/docs/reference/coroutines/composing-suspending-functions.html