iT邦幫忙

2021 iThome 鐵人賽

DAY 3
0

在上一篇文章中,我們知道如果我們要以非同步的方式來執行,可以使用 Thread + callback 來寫,不過使用 Callback 可能會發生兩個問題,一是 callback hell;另一則是控制權轉移 (Inversion of Control),讓程式碼變得不容易維護。

本篇文章,就讓我們嘗試使用 Coroutine 來改寫吧。

加上 Dependency

要在專案裡面使用 Coroutine ,必須先加上 dependency,如下:

dependencies{
	implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.5.2-native-mt")
}

如果是 Android 的話,需要使用下方的 dependency,因為 Coroutine 與 Android 的 Jetpack 有整合,所以使用下方的 dependency

  • Android
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)
}

改寫步驟:

  1. 先將函式改為 suspend function (暫停函式)
suspend fun login(userName: String, password: String): Token { ... }

suspend fun fetchLatestContentAsync(token: Token): List<Contents> { ... }
  1. 將原本的函式 showContents() 改成 Coroutine 的寫法
suspend fun showContents() = coroutineScope {
    launch {
        val token = login(userName, password)
        val content = fetch(token)
        withContext(Dispatchers.Main) {
            showContents(content)
        }
    }
}

到這邊我們就用 Coroutine 完成了一個非同步的程式,是不是很容易呢?看起來是不是很像是同步的程式碼呢?

回頭看一下我們做了哪些改動:

  1. 在非同步的函式中加上了 suspend
  2. showContents() 使用 coroutineScope 將程式碼包起來。
  3. login() 以及 fetch()launch{} 中執行。
  4. 最後,使用 withContext(Dispatchers.Main)showContents(content) 包起來。

來看一下這些改動分別代表什麼意思吧>>>

1. 在非同步的函式中加上了 suspend

Kotlin 的 Coroutine 用 suspend 這個關鍵字來宣告該函式為可暫停的函式。

2. showContents() 使用 coroutineScope 將程式碼包起來。

前面我們定義了 suspend function ,這些可暫停的函式只能在 Coroutine 中使用,而 coroutineScope 是用來建立一個新的範圍 (Scope),在 {} 內部的程式碼,就是稱為在 Coroutine 裏面的程式。

3. login() 以及 fetch()launch{} 中執行。

launch{} 的功能類似 coroutineScope ,它也是用來定義一個 Coroutine 的範圍,不過與 coroutineScope 不同的是,它會新建 coroutine ,而且它有一個回傳值 Job

4. 使用 withContext(Dispatchers.Main)showContents(content) 包起來

我們可以把 coroutine 想成是執行緒(thread)的概念,利用背景的執行緒來執行耗時動作,完成之後,在由主執行緒來繪製畫面。

前方的 launch{} 可以看作是建立一個新的執行緒,到了 withContext(Dispatchers.Main) 把 context 切回 Dispatchers.Main 也就是主執行緒,在主執行緒上進行畫面的更新。


使用 Coroutine 的好處

到這邊我們已經完成了一個簡單的 Coroutine 程式。我們注意到,使用 Coroutine,我們就不需要在非同步函式完成之後使用 Callback 將結果傳出來。不知道你有沒有發現,用 Coroutine 完成的程式碼可以讓非同步程式碼以同步的程式碼來撰寫。

另外,我們可以輕鬆的切換執行緒,利用不同的 Coroutine scope 來做不同的事,如上方的程式碼,我們使用 launchlogin()fetch() 讓它們在背景運算,並且在執行完畢之後,我們使用 withContext(Dispatchers.Main) 將執行緒切回主執行緒,並在主執行緒上更新畫面。

心智圖

心智圖

特別感謝

Kotlin Taiwan User Group
Kotlin 讀書會


由本系列文改編的《Kotlin 小宇宙:使用 Coroutine 優雅的執行非同步任務》已經上市囉。

有興趣的讀者歡迎參考:https://coroutine.kotlin.tips/
天瓏書局


上一篇
Day2:非同步執行與 Callback 的問題
下一篇
Day4:Coroutine 的四大特點
系列文
Coroutine 停看聽30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言