網站首頁 編程語言 正文
1.掛起函數
掛起函數在Kotlin協程中是一個比較重要的知識點,協程的非阻塞式、Channel、Flow等API都對它有充分的理解才能在學習時事半功倍。
已知的是Kotlin協程的特點是輕量和非阻塞, 單靠這兩個點就能說明Kotlin協程就的優勢嗎,不一定。這里先提出一個結論:掛起函數是Kotlin協程的最大優勢。 下面對于掛起函數的講解就圍繞這句話展開。
以獲取省市區Code為例,獲取區域Code的前提是要有城市Code,城市Code的前提是要有省份Code,請求結果通過CallBack返回。
public class RequestCode { public static void main(String[] args) { getProvincesCode(provincesCode -> { getCityCode(provincesCode, cityCode -> { getAreaCode(cityCode, areaCode -> { }); }); }); } /** * 獲取省份Code * * @param callBack */ private static void getProvincesCode(CallBack callBack) { callBack.onSuccess("100000"); } /** * 獲取城市Code * * @param provincesCode * @param callBack */ private static void getCityCode(String provincesCode, CallBack callBack) { callBack.onSuccess("100010"); } /** * 獲取區域code * * @param cityCode * @param callBack */ private static void getAreaCode(String cityCode, CallBack callBack) { callBack.onSuccess("100011"); } }
上面的代碼在開發中都遇到過,代碼可以優化的更簡潔更易讀,這里主要是證明掛起函數的優勢,就不做優化了。
上面的代碼可以看到三層嵌套是比較復雜的,如果再加上獲取國家、區域街道的話嵌套層級會更深,這樣就會對可讀性、可擴展性、可維護性都有影響。那么用Kotlin這個代碼要怎么寫呢?
現在我用delay(1000L)
替代CallBack
模擬網絡請求
fun main() = runBlocking { val provincesCode = getProvincesCode() val cityCode = getCityCode(provincesCode) val areaCode = getAreaCode(cityCode) } /** * 獲取省份Code * */ private suspend fun getProvincesCode(): String { withContext(Dispatchers.IO) { delay(1000L) } return "100000" } /** * 獲取城市Code * * @param provincesCode */ private suspend fun getCityCode(provincesCode: String): String { withContext(Dispatchers.IO) { delay(1000L) } return "100010" } /** * 獲取區域code * * @param cityCode */ private suspend fun getAreaCode(cityCode: String): String { withContext(Dispatchers.IO) { delay(1000L) } return "100011" }
Kotlin實現了同步方式完成異步請求,這種方式的實現要歸功于具體的函數的實現,在這三個函數中可以發現他們的定義與普通函數的區別是多了一個suspend
關鍵字,它的意思就是掛起。
再回頭看main
函數,前面加上了runBlocking
也就是說建立了一個協程的作用域,這是因為suspend
的出現,因為suspend
的作用就是掛起和恢復,而掛起和恢復是需要上下文的,因此就需要定義一個作用域,這里選擇了runBlocking
。
在函數中還有一個地方withContext(Dispatchers.IO)
這主要是執行線程的切換,除了IO
線程以外還有Main
、default
等線程。
在Kotlin中掛起和恢復是成對出現的,因為既然會被掛起那肯定也會被恢復,而協程的非阻塞也是因為掛起和恢復能力,那么掛起和恢復又是怎么樣的一個含義呢?
首先執行getProvincesCode()
這是一個掛起函數,因為用了Dispatchers.IO
切換到了IO線程因此這個等待時間就在IO線程執行,當等待時間結束后(CallBack回調結果)provincesCode
收到返回的結果,這個函數中suspend
掛起,main
函數中收到結果就是恢復能力,
我們看一下debug日志:
- 這是
getProvincesCode
的協程日志,可以獲取以下幾個信息:
- 當前協程的名字是:coroutine#1
- 協程運行在主線程:main
- 任務被掛起
withContext(Dispatchers.IO)
切換線程
任務切換到DefaultDispatcher-worker-1
線程繼續執行
-
return "省:100000"
回到主線程執行
我們總結一下上面的日志:
-
main
→DefaultDispatcher-worker-1
的過程是掛起; -
DefaultDispatcher-worker-1
→main
的過程是恢復;
那么掛起和恢復的含義也有非常明確了:
- 掛起: 只是將程序執行流程轉移到了其他線程,主線程并不會阻塞。我們知道Android中如果阻塞超過一定時間就會出現無響應,上面的代碼在運行過程中并不會導致無響應的發生,在代碼執行的過程中我們還可以做其他事情的,因為任務進入到子線程后主線程的狀態是空閑的。
- 恢復: 當子線程的任務執行完畢后再回到主線程的過程就叫做恢復。
掛起和恢復的能力是掛起函數特有的能力,普通函數時不具備的,如果在一個普通函數中僅僅加上suspend
關鍵字會發現其實并沒有什么用,編譯器會告知這種定義是多余的。
上面還提出了一個結論——掛起函數是Kotlin協程的最大優勢, 首先因為函數的執行過程中可以切換線程,其次函數的運行不會影響主線程導致主線程被阻塞。
2.深入理解suspend
上面掛起函數,掛起函數主要就是主線程切換到子線程,這個過程又是如何實現的?
已知掛起函數是依靠關鍵字suspend
,我們將帶有這個關鍵子的函數轉換成Java代碼進行分析:
private static final Object getProvincesCode(Continuation var0) { ... CoroutineContext var10000 = (CoroutineContext)Dispatchers.getIO(); ... return "省:100000"; }
代碼比較長,只保留了需要的地方。
-
getProvincesCode
里面多了一個參數Continuation
,意思是延續,而suspend
沒有了,那么Continuation
又是什么
public interface Continuation<in T> { /** * 協程的上下文. */ public val context: CoroutineContext /** * 恢復執行相應的協程,傳遞一個成功或失敗的[result]作為最后一個掛起點的返回值 */ public fun resumeWith(result: Result<T>) }
從Continuation
的源碼發現一個問題,它跟我們開頭的CallBack
是類似的,其中resumeWith
和onSucess
的功能也是一樣的,區別在于Continuation
是有一個泛型的參數
public interface CallBack { public void onSuccess(String response); } public interface Continuation<in T> { public fun resumeWith(result: Result<T>) }
由此可以得出結論:Continuation
本質上就是CallBack
只是多了一個帶有泛型的參數。
通過上面的分析可以得出結論 :掛起函數的本質就是Callback
我們已知Continuation
的本質是CallBack
,Continuation
是延續,就是接下來要做的事情,那么在省市區獲取的代碼其實就是這樣一個流程:
getProvincesCode(object : Continuation<String> { override fun resumeWith(result: Result<String>) { val provinceCode = result.getOrNull() println("provincesCode:$provinceCode") getCityCode(provinceCode, object : Continuation<String> { override fun resumeWith(result: Result<String>) { val cityCode = result.getOrNull() println("cityCode:$cityCode") getAreaCode(cityCode, object : Continuation<String> { override fun resumeWith(result: Result<String>) { val areaCode = result.getOrNull() println("areaCode:$areaCode") } }) } }) } })
這個過程是編譯器在后面幫我們做的,實際編碼中這種編碼方式并不會出現,畢竟掛起函數解決的就是這個問題。
3.協程與掛起函數
需要說明的是協程與掛起函數并不是同一個東西,我們再最開始使用suspend
實現省市區三級聯動的時候用到了runBlocking
,掛起函數的調用是需要一個協程作用域的,除了這點之外,在runBlocking
的源碼中還有一點要注意:
public actual fun <T> runBlocking( context: CoroutineContext, block: suspend CoroutineScope.() -> T): T { ... }
第二個參數中有一個suspend
,所以可以理解CoroutineScope.() -> T
也是一個掛起函數那么被suspend
關鍵字修飾的掛起函數可以運行在runBlocking
中也就不難理解了。
那么我們就可以得到一個結論:掛起和恢復是協程的底層能力,而掛起函數是一種表現形式,通過suspend關鍵字修飾的函數可以讓我們在上層很方便的實現這種底層能力。
4.掛起函數是Kotlin協程的最大優勢
開篇就提出了這個結論,最后再來總結一下這個能力:
- 掛起函數在執行中可以切換線程且不會對主線程造成阻塞;
- 掛起函數有掛起和恢復的能力,可以極大地簡化異步編程,實現同步執行異步任務;
- 掛起函數是Kotlin中特有的能力;
5.總結
- 定義一個掛起函數只需要在普通函數上加上
suspend
關鍵字,而添加這個關鍵字之后函數類型就會被改變,如suspend (Int) -> Double”與“(Int) -> Double
并不是同一個類型; - 掛起函數具有掛起和恢復的能力,那么就會出現同一行代碼會在兩個線程中執行,Kotlin編譯器會在后臺進行編譯;
- 掛起函數的本質就是CallBack。只是說,Kotlin 底層用了一個更加高大上的名字,叫 Continuation;
- 掛起和恢復是一種底層能力,而上層的表現形式是掛起函數;
- 掛起函數只能在協程中被調用或者在掛起函數中調用,而協程中的block也是一個掛起函數。
原文鏈接:https://juejin.cn/post/7172348568047452196
相關推薦
- 2022-08-03 如何一鍵理清大型Python項目依賴樹_python
- 2022-07-10 springboot 將logback日志根據不同類輸入到不同路徑下
- 2022-04-12 Python數據可視化Pyecharts庫的使用教程_python
- 2022-08-13 Kafka復習計劃 - Kafka基礎知識以及集群參方案和參數
- 2021-12-07 Linux系統的修復模式(單用戶模式)_Linux
- 2022-04-19 Windows中Python上傳文件到Liunx下的fastdfs
- 2022-11-30 深入理解Golang?channel的應用_Golang
- 2022-07-28 詳解Python中4種超參自動優化算法的實現_python
- 最近更新
-
- window11 系統安裝 yarn
- 超詳細win安裝深度學習環境2025年最新版(
- Linux 中運行的top命令 怎么退出?
- MySQL 中decimal 的用法? 存儲小
- get 、set 、toString 方法的使
- @Resource和 @Autowired注解
- Java基礎操作-- 運算符,流程控制 Flo
- 1. Int 和Integer 的區別,Jav
- spring @retryable不生效的一種
- Spring Security之認證信息的處理
- Spring Security之認證過濾器
- Spring Security概述快速入門
- Spring Security之配置體系
- 【SpringBoot】SpringCache
- Spring Security之基于方法配置權
- redisson分布式鎖中waittime的設
- maven:解決release錯誤:Artif
- restTemplate使用總結
- Spring Security之安全異常處理
- MybatisPlus優雅實現加密?
- Spring ioc容器與Bean的生命周期。
- 【探索SpringCloud】服務發現-Nac
- Spring Security之基于HttpR
- Redis 底層數據結構-簡單動態字符串(SD
- arthas操作spring被代理目標對象命令
- Spring中的單例模式應用詳解
- 聊聊消息隊列,發送消息的4種方式
- bootspring第三方資源配置管理
- GIT同步修改后的遠程分支