前言
Kotlin 是一門僅在标準庫中提供最基本底層 API 以便各種其他庫能夠利用協程的語言。與許多其他具有類似功能的語言不同,
async
與
await
在 Kotlin 中并不是關鍵字,甚至都不是标準庫的一部分。此外,Kotlin 的 挂起函數 概念為異步操作提供了比 future 與 promise 更安全、更不易出錯的抽象。
kotlinx.coroutines
是由 JetBrains 開發的功能豐富的協程庫。它包含本指南中涵蓋的很多啟用進階協程的原語,包括
launch
、
async
等等。
如需了解其他kotlin用法,可檢視如下:
Android Kotlin實戰之高階使用泛型擴充協程懶加載詳解_蝸牛、Z的部落格-CSDN部落格
Android kotlin在實戰過程問題總結與開發技巧詳解_kotlin 同步鎖_蝸牛、Z的部落格-CSDN部落格
Kotlin文法詳解與實踐教程,區分JAVA以及如何閉坑_kotlin mapof可以嵌套 to_蝸牛、Z的部落格-CSDN部落格
什麼是協程suspend
suspend的字面含義是暫停、挂起的意思。在kotlin中,代碼執行到 suspend 函數的時候會『挂起』,并且這個『挂起』是非阻塞式的,它不會阻塞你目前的線程,挂起的定位: 暫時切走,稍後再切回來,和java的Thread.sleep()不一樣,一個是阻塞,一個是等待,類似wait。
阻塞的基本原理一直占用CUP,不讓出如果sleep,挂起的是将自己的cup時間讓出去,等待重新配置設定。這樣就很好了解了。用最直覺的了解是一種異步操作
協程方法的調用
如果目前方法被suspend修飾了,如果不是通過runblock或者
coroutines
庫調用,那麼調用鍊上的所有函數都要背suspend修飾
1.修飾方法
suspend fun main() {
delay(1000)
println("hello")
}
又因為我們在業務中,很難會擷取到main方法,是以無法通過main方法在源頭解決協程的調用
2.借助工具類
2.1同步方法runBlocking
runBlocking是阻塞調用,如果通過它來處理協程,那麼隻有當runBlocking的方法體中執行完,才會往下執行,會一直霸占cpu,直到運作完才會釋放。
fun main() {
runBlocking {
delay(1000)
println("hello---block")
}
println("hello")
}
2.2異步庫 kotlinx.coroutines
kotlinx.coroutines
異步庫的調用是先挂起線程代碼,繼續執行,等下面執行完才會又回到協程函數執行
fun main() {
GlobalScope.launch { // 在背景啟動一個新的協程并繼續
println("suspend start!") // 在延遲後列印輸出
delay(1000L) // 非阻塞的等待 1 秒鐘(預設時間機關是毫秒)
println("suspend end!") // 在延遲後列印輸出
}
println("sleep start,") // 協程已在等待時主線程還在繼續
Thread.sleep(2000L) // 阻塞主線程 2 秒鐘來保證 JVM 存活
println("sleep end,") // 協程已在等待時主線程還在繼續
}
通過以上日志分析,協程提都不會被執行,被挂起了,先執行了下面的代碼,遇到sleep函數,形成阻塞,協程又擷取到cpu的時間,開始執行協程代碼快,執行完,sleep阻塞完成又繼續執行。
coroutines
庫的使用
coroutines
kotlinx.coroutines是填補了kotlin在Java中的缺失,也提供了異步處理協程的問題。
依賴庫:
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.1"
使用
1、異步挂起協程
GlobalScope.launch { // 在背景啟動一個新的協程并繼續
//協程代碼調用處
}
2、延遲
GlobalScope.launch { // 在背景啟動一個新的協程并繼續
delay(1000L) // 非阻塞的等待 1 秒鐘(預設時間機關是毫秒)
}
直接調用delay,設定需要阻塞延遲的時間
3、工作對象Job
launch會傳回一個對象Job,job就是目前的任務,如果你接受了這個Job,那麼代碼塊将不會執行,需要你手動調用
fun main() {
val job = GlobalScope.launch { // 啟動一個新協程并保持對這個作業的引用
delay(1000L)
println("World!")
}
println("Hello,")
runBlocking {
job.join() // 等待直到子協程執行結束
}
}
job的join隻能在阻塞方法中調用,或者suspend方法體,不能在
GlobalScope.launch {
job.join()
}
調用,否則不會執行目前代碼塊。因為launch已傳回了一個job,目前job沒有執行,是以,改job一直處于一個未執行狀态。
也可以取消:job.cancel()
協程作用域:launch{}
GlobalScope與CoroutineScope對比
八股文顯示,前者比後者快,通過源碼分析,GlobalScope是繼承了CoroutineScope,是對CoroutineScope的封裝,提供一個靜态函數。
結構化的并發
協程的實際使用還有一些需要改進的地方。 當我們使用
GlobalScope.launch
時,我們會建立一個頂層協程。雖然它很輕量,但它運作時仍會消耗一些記憶體資源。有一個更好的解決辦法。我們可以在代碼中使用結構化并發。 我們可以在執行操作所在的指定作用域内啟動協程, 而不是像通常使用線程(線程總是全局的)那樣在 GlobalScope 中啟動.
fun main() = runBlocking { // this: CoroutineScope
launch { // 在 runBlocking 作用域中啟動一個新協程
delay(1000L)
println("World!")
}
println("Hello,")
}
作用域建構器
除了由不同的建構器提供協程作用域之外,還可以使用 coroutineScope 建構器聲明自己的作用域。它會建立一個協程作用域并且在所有已啟動子協程執行完畢之前不會結束.
runBlocking 與 coroutineScope 可能看起來很類似,因為它們都會等待其協程體以及所有子協程結束。 主要差別在于,runBlocking 方法會阻塞目前線程來等待, 而 coroutineScope 隻是挂起,會釋放底層線程用于其他用途。 由于存在這點差異,runBlocking 是正常函數,而 coroutineScope 是挂起函數
fun main() = runBlocking { // this: CoroutineScope
launch {
delay(200L)
println("Task from runBlocking 1")
}
coroutineScope { // 建立一個協程作用域
launch {
delay(500L)
println("Task from nested launch 2")
}
delay(100L)
println("Task from coroutine scope 3") // 這一行會在内嵌 launch 之前輸出
}
println("Coroutine scope is over 4") // 這一行在内嵌 launch 執行完畢後才輸出
}
挂起 :GlobalScope.async
GlobalScope的async異步挂起,支援傳回值,且不能單獨直接使用,需在runBlocking體内執行。
fun main()= runBlocking {
println("current thread 1")
//launch啟動背景排程線程,并且不堵塞目前線程
val deferred = GlobalScope.async {
println("async thread 2=")
delay(1000)
println("async end 3")
//需要通過标簽的方式傳回
[email protected] "123"
}
println("current thread end 4")
val result = deferred.await()
println("result 5= $result")
//目前線程休眠以便排程線程有機會執行
Thread.sleep(3000)
}
協程上下文與排程器
協程上下文包含一個 協程排程器 (參見 CoroutineDispatcher)它确定了相關的協程在哪個線程或哪些線程上執行。協程排程器可以将協程限制在一個特定的線程執行,或将它分派到一個線程池,亦或是讓它不受限地運作。
launch { // 運作在父協程的上下文中,即 runBlocking 主協程
println("main runBlocking : I'm working in thread ${Thread.currentThread().name}")
}
launch(Dispatchers.Unconfined) { // 不受限的——将工作在主線程中
println("Unconfined : I'm working in thread ${Thread.currentThread().name}")
}
launch(Dispatchers.Default) { // 将會擷取預設排程器
println("Default : I'm working in thread ${Thread.currentThread().name}")
}
launch(newSingleThreadContext("MyOwnThread")) { // 将使它獲得一個新的線程
println("newSingleThreadContext: I'm working in thread ${Thread.currentThread().name}")
}
排程器類型如下:
public actual object Dispatchers {
@JvmStatic
public actual val Default: CoroutineDispatcher = DefaultScheduler
@JvmStatic
public actual val Main: MainCoroutineDispatcher get() = MainDispatcherLoader.dispatcher
@JvmStatic
public actual val Unconfined: CoroutineDispatcher = kotlinx.coroutines.Unconfined
@JvmStatic
public val IO: CoroutineDispatcher = DefaultIoScheduler
@DelicateCoroutinesApi
public fun shutdown() {
DefaultExecutor.shutdown()
// Also shuts down Dispatchers.IO
DefaultScheduler.shutdown()
}
}
調試協程與線程
協程可以在一個線程上挂起并在其它線程上恢複。 如果沒有特殊工具,甚至對于一個單線程的排程器也是難以弄清楚協程在何時何地正在做什麼事情。
啟動方式
runBlocking | 建立新的協程,運作在目前線程上,是以會堵塞目前線程,直到協程體結束 |
GlobalScope.launch | 啟動一個新的線程,在新線程上建立運作協程,不堵塞目前線程 |
GlobalScope.asyn | 啟動一個新的線程,在新線程上建立運作協程,并且不堵塞目前線程,支援 通過await擷取傳回值 |