iT邦幫忙

2021 iThome 鐵人賽

DAY 6
0

好的好的,經過了前幾篇文章之後,想必大家應該對 Coroutine 有一些了解了吧,我在這邊快速複習一下。

Coroutine 是用來解決非同步程式的執行問題,使用它,你就可以避免使用 Callback,也就不會發生 回調地獄(Callback hell)、控制權轉移(Inversion of control)的情況。它在官網是以輕量的執行緒介紹。同時建立相同數量的 Coroutine 與執行緒,就可以立刻發現兩者的執行速度差異。

接下來要提到的是,它是屬於無棧協程,也就是說,在 Coroutine 內部不像執行緒一樣使用調用棧 (call stack) 用來儲存 Coroutine 的上下文,在 Coroutine 的內部是使用 Callback 的機制在進行非同步處理,只不過 Coroutine 把這層包起來了。所以我們感覺不出來是用 Callback 機制。

另外, 在 Coroutine 中,我們將耗時、延時的任務定義在 suspend 函式中,而這個 suspend 函式必須要在 Coroutine scope 中執行,無法在 Coroutine scope 以外呼叫這個 suspend 函式,原因無他,就是因為在 suspend 函式裡面有包含了 Continuation 的實例,而這個實例包含著 Coroutine 上下文,當完成 suspend 函式之後,就可以調用 Continuation 中的 resumeWith 來把 Coroutine 切換回來。這就是我說的 Callback 機制。

要如何建立一個 Coroutine 呢?

根據前面的描述,Kotlin 的 Coroutine 必須要在一個稱作 Coroutine Scope 的範圍內才能運作。除了需要一個 Coroutine Scope 以外,在這個範圍內,如果需要執行一個非同步的程式, Kotlin 提供了一個關鍵字 suspend 用來定義這個函式是一個需要暫停的函式。最後,我們知道 Coroutine 其實也是運行在執行緒中,那麼我們也可以依照我們的需求切換執行緒,在 Kotlin 的 Coroutine 是使用調度器 (Dispatchers) 來切換不同的執行緒。

三個重要要素:

3 elements

  1. CoroutineScope:Coroutine 只能夠在一範圍內才能夠被執行。
  2. Suspend function:用來定義非同步函式,調用該函式時,協程便會暫停,直到程式完成其工作時,才會在暫停的地方恢復。
  3. Dispatchers:根據不同的 Dispatchers,Coroutine 會選擇適當的執行緒來執行相對應的工作。

CoroutineScope

有三種建立 CoroutineScope 的方式

  1. coroutineScope():建立一個 Coroutine Scope,但是不建立新的 Coroutine。也就是說它內部的 coroutineContext 是由繼承外部的 CoroutineScope 得來的。目的是用來分解並行工作的。
  2. launch():建立一個新的 Coroutine,會回傳一個 Job ,我們可以在 launch() 中直接執行任務,或是透過回傳的 Job 在適當的時機呼叫其 start() 或者 join() 延遲執行。適合使用在沒有回傳值的非同步任務。
  3. async():如果非同步任務是有回傳值的,那就需要使用 async() 來建立 Coroutine Scope, async() 的回傳值將會回傳一個 Deferred<T> ,當任務完成時,我們可以使用 await() 來取得回傳值得內容。

Suspend function

Kotlin 的 coroutine 使用 suspend 關鍵字用來把函式定義成可暫停的 (suspendable)。

suspend fun suspendFun() = coroutineScope {
		doSomething()
}

如上,在 suspendFun() 前方加上 suspend 就會讓這個函式變成可暫停的函式,也就可以在 Coroutine scope 中使用,並在調用時暫停,完成任務時在原地恢復。

在 suspend 函式中,我們除了可以調用其他的 suspend 函式,當然也可以調用普通的函式。

Coroutine 定義了幾個 top-level 的 suspend function 供我們使用,如下:

  • delay() :暫停 Coroutine ,與執行緒的 Thread.sleep() 不同,不會停止執行緒。
  • yield() :把目前 Coroutine 調度器執行的任務暫停並讓給其他的任務來執行。
  • withContext() :在當下的 Coroutine scope 中,使用不同的 context。
  • withTimeout() :設定執行時間,如果時間到還沒有完成,就會拋出例外。
  • withTimeoutOrNull() :同上,只不過是回傳一個 Null 值。
  • awaitAll() :等待所有的 Deferred 完成,或是其中一個發生錯誤。
  • joinAll() :暫停 Coroutine 直到所有的 Job 都完成。等同於 jobs.forEach { it.join() }.

Dispatchers

調度器是用來決定該 Coroutine 要在哪個執行緒來執行。如果沒有設定,將會使用 Dispatchers.Default。

有四種 Dispatchers,如下:

  • Dispatchers.Default
  • Dispatchers.Main
  • Dispatchers.IO:將阻塞 IO 任務在共享執行緒池執行。
  • Dispatchers.Unconfined

簡單來說:Dispatchers.Default 是用在背景運算的 coroutine ,而 Dispatchers.Main 是用在畫面更新上(主執行緒)。

小結

Coroutine 是由三個要素所組成,CoroutineScope、suspend fun 以及 Dispatchers。suspend 函式 只能在 CoroutineScope 中執行,根據函式是否有回傳值,我們可以選擇使用 launch() 或是 async() 來建立一個 coroutineScope,前者是沒有回傳值的,後者則是有一個回傳值的。

除了我們自己定義的 suspend 函式以外,Coroutine 也有定義了一些 top-level suspend 函式。如 delay()

最後,每一個 CoroutineScope 都可以帶入 CoroutineContext,如果沒有帶入,那麼就會使用預設的值,預設的 CoroutineContext 是 Dispatchers.Default。

心智圖

心智圖

特別感謝

Kotlin Taiwan User Group

Kotlin 讀書會


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

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


上一篇
Day5:深入認識 Coroutine
下一篇
Day7:CoroutineScope:launch() 以及 async()
系列文
Coroutine 停看聽30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言