android kotlin 協程(一) 基礎入門
theme: github
android kotlin 協程(一)
config:
-
system: macOS
-
android studio: 2022.1.1 Electric Eel
- gradle: gradle-7.5-bin.zip
- android build gradle: 7.1.0
- Kotlin coroutine core: 1.6.4
前言:最近系統的學習了一遍協程, 計劃通過10篇左右blog來記錄一下我對協程的理解, 從最簡單的 runBlocking開始; 到最後 suspend和continuation的關係等等
tips:前面幾篇全都是協程的基本使用,沒有原始碼,等後面對協程有個基本理解之後,才會簡單的分析一下原始碼!
學習我這個系列的協程, 只需要記住一點, suspend函式 永遠不會阻塞main執行緒執行! 永遠是非同步的!
看完本篇你將會學到哪些知識:
- runBlocking()
- CoroutineScope#launch()
- CoroutineScope#async()
- Job的常用方法
- 協程狀態[isActive,isCancelled,isCompleted]
runBlocking
定義: runBlocking 會阻塞執行緒來等待自己子協程執行完, 並且對於不是子協程的作用域,也會盡量的去執行,
首先來了解一下什麼是自己的子協程
通常我們通過
- CoroutineScope.launch{}
- CoroutineScope.async{}
來開啟一個協程,因為當前是在CoroutineScope作用域中,所以直接launch / async 即可
這段程式碼可以看出,runBlocking 會等待子協程全部執行完,然後在結束任務,因為協程都是非同步的,
所以會先執行協程之外的程式碼,然後再執行協程中的程式碼
可以在協程中新增一些睡眠操作再來測試一下
可以看出,還是可以正常的執行完所有程式碼
現在解釋完了定義中的前半句話: runBlocking 會阻塞執行緒來等待自己子協程執行完, 並且對於不是子協程的作用域,也會盡量的去執行,
再來看一下後半句話:
可以看出,通過自定義coroutine 和 GlobalScope,來建立的協程照樣可以執行出來
那麼在他們之中稍微新增一點邏輯會怎麼樣?
可以看出,一旦添加了一點邏輯, runBlocking是不會等待非子協程的作用域
如果想讓runBlocking等待非子協程執行完,那麼只需要呼叫Job.#join() 即可
例如這樣:
join()方法目前可以理解為: 等待當前協程執行完 在往下執行其他程式碼,
一旦呼叫了join()方法,那麼協程就變成了同步的,那麼這塊程式碼一共執行需要4s
因為協程1並沒有join, 所以協程1還是非同步的,
協程2呼叫了join,所以在執行協程2的過程中,協程1也在執行.
所以協程1,與協程2的執行時間為2s
tips: 在開發中不建議使用runBlocking,因為會阻塞主執行緒,阻塞主執行緒的時間,用來子協程的執行..
開啟協程兩種不同的方式
在上面程式碼中我們提到了,開啟協程有2種方式
- CoroutineScope#launch{}
- CoroutineScope#async{}
先來看相同點:
相同點就是無論是哪種方式,都會執行裡面的程式碼
那麼這兩種方式有什麼區別呢?
- launch無法返回資料, async可以返回結果
返回的結果通過 Deferred#await()
來獲取,並且呼叫Deferred#await()
的時候,會等待async{} 執行完成之後在往下執行,就和Job#join
一樣,不過await()
有返回結果
使用await的時候有一個注意點:
那麼也可以看到,launch{} 與 async{} 的返回值也有所不同:
- launch{} 返回 Job
- async{} 返回Deferred
其實本質上來說,async 返回的也是Job,不過只是Job的子類Deferred而已,Deferred只是對返回值等一些操作的封裝
那麼Job是用來幹什麼的呢?
Job是用來管理協程的生命週期的, 例如剛才提到的 Job.join() 就可以讓協程 “立即執行”
launch{} 和 async{} 捕獲異常的方式也不同,這個等下一篇專門聊異常的時候在詳細講解
Job.cancel 取消協程
協程比執行緒好用的一點就是協程可以自己管理生命週期, 而執行緒則不可以
這裡需要注意的是,如果協程體中還在執行,但是外部已經取消了,那麼則會throw異常出來
JobCancellationException
```kotlin
fun main() = runBlocking
val job = launch {
try {
(0..100).forEachIndexed { index, _ ->
delay(1000)
println("launch $index")
}
} catch (e: Exception) {
println("協程被取消了 $e")
}
}
// 協程執行完成監聽
job.invokeOnCompletion {
println("協程執行完畢${it}")
}
delay(5500)
// 取消協程
job.cancel()
println("main end")
} ```
Job#invokeOnCompletion: 協程完成回撥
執行結果:
main start
launch 0
launch 1
launch 2
launch 3
launch 4
main end
協程被取消了 kotlinx.coroutines.JobCancellationException: StandaloneCoroutine was cancelled; job="coroutine#2":StandaloneCoroutine{Cancelling}@76a4d6c
協程執行完畢kotlinx.coroutines.JobCancellationException: StandaloneCoroutine was cancelled; job="coroutine#2":StandaloneCoroutine{Cancelled}@76a4d6c
coroutine的3種狀態
coroutine的三種狀態都是通過Job來管理的:
- isActive 是否是活躍狀態
- isCancelled 是否取消
- isCompleted 是否完成
先來看看正常流程執行的程式碼:
我們知道協程始終是非同步執行的,在執行printlnJob的時候,協程體中的程式碼還沒有真正的執行
所以此時處於活躍狀態,並且協程沒有被執行完
如果我們在協程執行完成的回撥中呼叫
那麼此時,協程體中的程式碼已經執行完了,那麼此時就是非活躍狀態
還剩一個Job#isCancelled 這個方法比較簡單,簡單的說就是是否呼叫了Job.cancel()
但是這裡有一個特別奇怪的點,明明已經呼叫Job#cancel() 來取消協程,並且協程體中的程式碼也沒執行,但是為什麼還顯示協程沒有執行完呢?
因為Job#cancel() 並不是suspend函式,不是suspend函式就沒有恢復功能,這行文字可能看的有一點迷惑,先不用管什麼掛起於恢復,現在只需要知道
我們呼叫cancel() 的時候會緊跟著一個,Job#join() 即可
或者直接呼叫Job.cancelAndJoin() 即可
掛起恢復,這4個字我理解了10天左右,不可能通過本篇就講清楚,現在只需要會呼叫這些api,即可!!
那麼問題就來了,這個狀態有什麼用呢?
先來看一段程式碼:
可以驚奇的發現,這段程式碼無論如何都cancel不掉.好像是失效了一樣
那麼解決這個問題,就可以檢測協程是否是活躍狀態,例如這樣
Job也提供了一個方法: Job#ensureActive()
ensureActive() 本質也是通過isActive判斷,不同的是,當取消的時候可以捕獲到取消的異常,然後來處理對應的事件
圖片地址: http://gitee.com/lanyangyangzzz/picture/blob/master/coroutine/coroutine1/Feb-09-2023%2015-53-05.gif
回顧一下本篇:
本篇我們講解了runBlocking, 這個函式會幫我們阻塞主執行緒, 阻塞住執行緒的時候會等待內部的子協程全部執行完
還聊了最基礎的如何開啟一個協程, launch / async 以及他們的相同點和不同點
最後引出了協程生命週期管理者Job, 講解了Job常用的方法,以及job的3種狀態
| 方法名 | 作用 | 補充 | | -------------------- | -------------------- | ------------------------------------------------------------ | | join() | 立即恢復協程體執行 | 等待協程體執行完成,在執行後續程式碼 | | cancel() | 取消協程 | ,如果取消時,協程體還在執行,這throw JobCancellationException,這個異常不會上報,會自行處理 | | invokeOnCompletion() | 協程體執行完成回撥 | | | isActive | 協程體是否是活躍狀態 | | | isCancelled | 協程體是否被取消 | | | isCompleted | 協程體是否執行完成 | |
下一篇預告:
- CoroutineDispatcher // 協程排程器 用來切換執行緒
-
CoroutineName // 協程名字
-
CoroutineStart // 協程啟動模式
- CoroutineException // launch / async 捕獲異常
- GlobalCoroutineException // 全域性捕獲異常
原創不易,您的點贊就是我最大的支援!