在上一篇文章中,我們知道如果我們要以非同步的方式來執行,可以使用 Thread + callback 來寫,不過使用 Callback 可能會發生兩個問題,一是 callback hell;另一則是控制權轉移 (Inversion of Control),讓程式碼變得不容易維護。
本篇文章,就讓我們嘗試使用 Coroutine 來改寫吧。
要在專案裡面使用 Coroutine ,必須先加上 dependency,如下:
dependencies{
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2-native-mt")
}
如果是 Android 的話,需要使用下方的 dependency,因為 Coroutine 與 Android 的 Jetpack 有整合,所以使用下方的 dependency
dependencies {
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.2")
}
前面我們定義了三個函式如下:
fun login(userName: String, password: String): Token { ... }
fun fetchLatestContent(token: Token): List<Contents> { ... }
fun showContents(contents: List<Contents>){ ... }
fun showContents() {
val token = login(userName, password)
val contents = fetchLatestContents(token)
showContents(contents)
}
suspend fun login(userName: String, password: String): Token { ... }
suspend fun fetchLatestContentAsync(token: Token): List<Contents> { ... }
showContents()
改成 Coroutine 的寫法suspend fun showContents() = coroutineScope {
launch {
val token = login(userName, password)
val content = fetch(token)
withContext(Dispatchers.Main) {
showContents(content)
}
}
}
到這邊我們就用 Coroutine 完成了一個非同步的程式,是不是很容易呢?看起來是不是很像是同步的程式碼呢?
回頭看一下我們做了哪些改動:
suspend
。showContents()
使用 coroutineScope
將程式碼包起來。login()
以及 fetch()
在 launch{}
中執行。withContext(Dispatchers.Main)
將 showContents(content)
包起來。suspend
Kotlin 的 Coroutine 用 suspend
這個關鍵字來宣告該函式為可暫停的函式。
showContents()
使用 coroutineScope
將程式碼包起來。前面我們定義了 suspend function
,這些可暫停的函式只能在 Coroutine 中使用,而 coroutineScope
是用來建立一個新的範圍 (Scope),在 {}
內部的程式碼,就是稱為在 Coroutine 裏面的程式。
login()
以及 fetch()
在 launch{}
中執行。launch{}
的功能類似 coroutineScope
,它也是用來定義一個 Coroutine 的範圍,不過與 coroutineScope
不同的是,它會新建 coroutine ,而且它有一個回傳值 Job
。
withContext(Dispatchers.Main)
將 showContents(content)
包起來我們可以把 coroutine 想成是執行緒(thread)的概念,利用背景的執行緒來執行耗時動作,完成之後,在由主執行緒來繪製畫面。
前方的 launch{}
可以看作是建立一個新的執行緒,到了 withContext(Dispatchers.Main)
把 context 切回 Dispatchers.Main
也就是主執行緒,在主執行緒上進行畫面的更新。
到這邊我們已經完成了一個簡單的 Coroutine 程式。我們注意到,使用 Coroutine,我們就不需要在非同步函式完成之後使用 Callback 將結果傳出來。不知道你有沒有發現,用 Coroutine 完成的程式碼可以讓非同步程式碼以同步的程式碼來撰寫。
另外,我們可以輕鬆的切換執行緒,利用不同的 Coroutine scope 來做不同的事,如上方的程式碼,我們使用 launch
讓 login()
、 fetch()
讓它們在背景運算,並且在執行完畢之後,我們使用 withContext(Dispatchers.Main)
將執行緒切回主執行緒,並在主執行緒上更新畫面。
Kotlin Taiwan User Group
Kotlin 讀書會
有興趣的讀者歡迎參考:https://coroutine.kotlin.tips/
天瓏書局