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 來做不同的事,如上方的程式碼,我們使用 launch 讓 login() 、 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) }}
直播中

尚未有邦友留言

立即登入留言