Kotlin 協(xié)程與掛起函數(shù)及suspend關鍵字深入理解
1.掛起函數(shù)
掛起函數(shù)在Kotlin協(xié)程中是一個比較重要的知識點,協(xié)程的非阻塞式、Channel、Flow等API都對它有充分的理解才能在學習時事半功倍。
已知的是Kotlin協(xié)程的特點是輕量和非阻塞, 單靠這兩個點就能說明Kotlin協(xié)程就的優(yōu)勢嗎,不一定。這里先提出一個結論:掛起函數(shù)是Kotlin協(xié)程的最大優(yōu)勢。 下面對于掛起函數(shù)的講解就圍繞這句話展開。
以獲取省市區(qū)Code為例,獲取區(qū)域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");
}
/**
* 獲取區(qū)域code
*
* @param cityCode
* @param callBack
*/
private static void getAreaCode(String cityCode, CallBack callBack) {
callBack.onSuccess("100011");
}
}
上面的代碼在開發(fā)中都遇到過,代碼可以優(yōu)化的更簡潔更易讀,這里主要是證明掛起函數(shù)的優(yōu)勢,就不做優(yōu)化了。
上面的代碼可以看到三層嵌套是比較復雜的,如果再加上獲取國家、區(qū)域街道的話嵌套層級會更深,這樣就會對可讀性、可擴展性、可維護性都有影響。那么用Kotlin這個代碼要怎么寫呢?
現(xiàn)在我用delay(1000L)替代CallBack模擬網(wǎng)絡請求
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"
}
/**
* 獲取區(qū)域code
*
* @param cityCode
*/
private suspend fun getAreaCode(cityCode: String): String {
withContext(Dispatchers.IO) {
delay(1000L)
}
return "100011"
}
Kotlin實現(xiàn)了同步方式完成異步請求,這種方式的實現(xiàn)要歸功于具體的函數(shù)的實現(xiàn),在這三個函數(shù)中可以發(fā)現(xiàn)他們的定義與普通函數(shù)的區(qū)別是多了一個suspend關鍵字,它的意思就是掛起。
再回頭看main函數(shù),前面加上了runBlocking也就是說建立了一個協(xié)程的作用域,這是因為suspend的出現(xiàn),因為suspend的作用就是掛起和恢復,而掛起和恢復是需要上下文的,因此就需要定義一個作用域,這里選擇了runBlocking。
在函數(shù)中還有一個地方withContext(Dispatchers.IO)這主要是執(zhí)行線程的切換,除了IO線程以外還有Main、default等線程。
在Kotlin中掛起和恢復是成對出現(xiàn)的,因為既然會被掛起那肯定也會被恢復,而協(xié)程的非阻塞也是因為掛起和恢復能力,那么掛起和恢復又是怎么樣的一個含義呢?
首先執(zhí)行getProvincesCode()這是一個掛起函數(shù),因為用了Dispatchers.IO切換到了IO線程因此這個等待時間就在IO線程執(zhí)行,當?shù)却龝r間結束后(CallBack回調結果)provincesCode收到返回的結果,這個函數(shù)中suspend掛起,main函數(shù)中收到結果就是恢復能力,
我們看一下debug日志:
- 這是
getProvincesCode的協(xié)程日志,可以獲取以下幾個信息:
- 當前協(xié)程的名字是:coroutine#1
- 協(xié)程運行在主線程:main
- 任務被掛起

withContext(Dispatchers.IO)切換線程
任務切換到DefaultDispatcher-worker-1線程繼續(xù)執(zhí)行

return "?。?00000"回到主線程執(zhí)行

我們總結一下上面的日志:
main→DefaultDispatcher-worker-1的過程是掛起;DefaultDispatcher-worker-1→main的過程是恢復;
那么掛起和恢復的含義也有非常明確了:
- 掛起: 只是將程序執(zhí)行流程轉移到了其他線程,主線程并不會阻塞。我們知道Android中如果阻塞超過一定時間就會出現(xiàn)無響應,上面的代碼在運行過程中并不會導致無響應的發(fā)生,在代碼執(zhí)行的過程中我們還可以做其他事情的,因為任務進入到子線程后主線程的狀態(tài)是空閑的。
- 恢復: 當子線程的任務執(zhí)行完畢后再回到主線程的過程就叫做恢復。
掛起和恢復的能力是掛起函數(shù)特有的能力,普通函數(shù)時不具備的,如果在一個普通函數(shù)中僅僅加上suspend關鍵字會發(fā)現(xiàn)其實并沒有什么用,編譯器會告知這種定義是多余的。
上面還提出了一個結論——掛起函數(shù)是Kotlin協(xié)程的最大優(yōu)勢, 首先因為函數(shù)的執(zhí)行過程中可以切換線程,其次函數(shù)的運行不會影響主線程導致主線程被阻塞。
2.深入理解suspend
上面掛起函數(shù),掛起函數(shù)主要就是主線程切換到子線程,這個過程又是如何實現(xiàn)的?
已知掛起函數(shù)是依靠關鍵字suspend,我們將帶有這個關鍵子的函數(shù)轉換成Java代碼進行分析:
private static final Object getProvincesCode(Continuation var0) {
...
CoroutineContext var10000 = (CoroutineContext)Dispatchers.getIO();
...
return "省:100000";
}
代碼比較長,只保留了需要的地方。
getProvincesCode里面多了一個參數(shù)Continuation,意思是延續(xù),而suspend沒有了,那么Continuation又是什么
public interface Continuation<in T> {
/**
* 協(xié)程的上下文.
*/
public val context: CoroutineContext
/**
* 恢復執(zhí)行相應的協(xié)程,傳遞一個成功或失敗的[result]作為最后一個掛起點的返回值
*/
public fun resumeWith(result: Result<T>)
}
從Continuation的源碼發(fā)現(xiàn)一個問題,它跟我們開頭的CallBack是類似的,其中resumeWith和onSucess的功能也是一樣的,區(qū)別在于Continuation是有一個泛型的參數(shù)
public interface CallBack {
public void onSuccess(String response);
}
public interface Continuation<in T> {
public fun resumeWith(result: Result<T>)
}
由此可以得出結論:Continuation本質上就是CallBack只是多了一個帶有泛型的參數(shù)。
通過上面的分析可以得出結論 :掛起函數(shù)的本質就是Callback
我們已知Continuation的本質是CallBack,Continuation是延續(xù),就是接下來要做的事情,那么在省市區(qū)獲取的代碼其實就是這樣一個流程:
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")
}
})
}
})
}
})
這個過程是編譯器在后面幫我們做的,實際編碼中這種編碼方式并不會出現(xiàn),畢竟掛起函數(shù)解決的就是這個問題。
3.協(xié)程與掛起函數(shù)
需要說明的是協(xié)程與掛起函數(shù)并不是同一個東西,我們再最開始使用suspend實現(xiàn)省市區(qū)三級聯(lián)動的時候用到了runBlocking,掛起函數(shù)的調用是需要一個協(xié)程作用域的,除了這點之外,在runBlocking的源碼中還有一點要注意:
public actual fun <T> runBlocking(
context: CoroutineContext,
block: suspend CoroutineScope.() -> T): T {
...
}
第二個參數(shù)中有一個suspend,所以可以理解CoroutineScope.() -> T也是一個掛起函數(shù)那么被suspend關鍵字修飾的掛起函數(shù)可以運行在runBlocking中也就不難理解了。
那么我們就可以得到一個結論:掛起和恢復是協(xié)程的底層能力,而掛起函數(shù)是一種表現(xiàn)形式,通過suspend關鍵字修飾的函數(shù)可以讓我們在上層很方便的實現(xiàn)這種底層能力。
4.掛起函數(shù)是Kotlin協(xié)程的最大優(yōu)勢
開篇就提出了這個結論,最后再來總結一下這個能力:
- 掛起函數(shù)在執(zhí)行中可以切換線程且不會對主線程造成阻塞;
- 掛起函數(shù)有掛起和恢復的能力,可以極大地簡化異步編程,實現(xiàn)同步執(zhí)行異步任務;
- 掛起函數(shù)是Kotlin中特有的能力;
5.總結
- 定義一個掛起函數(shù)只需要在普通函數(shù)上加上
suspend關鍵字,而添加這個關鍵字之后函數(shù)類型就會被改變,如suspend (Int) -> Double”與“(Int) -> Double并不是同一個類型; - 掛起函數(shù)具有掛起和恢復的能力,那么就會出現(xiàn)同一行代碼會在兩個線程中執(zhí)行,Kotlin編譯器會在后臺進行編譯;
- 掛起函數(shù)的本質就是CallBack。只是說,Kotlin 底層用了一個更加高大上的名字,叫 Continuation;
- 掛起和恢復是一種底層能力,而上層的表現(xiàn)形式是掛起函數(shù);
- 掛起函數(shù)只能在協(xié)程中被調用或者在掛起函數(shù)中調用,而協(xié)程中的block也是一個掛起函數(shù)。

以上就是Kotlin 協(xié)程與掛起函數(shù)及suspend關鍵字深入理解的詳細內容,更多關于Kotlin 協(xié)程掛起函數(shù)suspend的資料請關注腳本之家其它相關文章!
相關文章
Android studio將Module打包成Jar的方法
這篇文章主要介紹了Android studio將Module打包成Jar的方法,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-10-10
android開發(fā)環(huán)境搭建詳解(eclipse + android sdk)
這篇文章主要介紹了android開發(fā)環(huán)境搭建詳解(eclipse + android sdk),需要的朋友可以參考下2014-05-05
Android 開源項目側邊欄菜單(SlidingMenu)使用詳解
SlidingMenu的是一種比較新的設置界面或配置界面效果,在主界面左滑或者右滑出現(xiàn)設置界面,能方便的進行各種操作.目前有大量的應用都在使用這一效果。如Evernote、Google+、Foursquare等,國內的豌豆夾,人人,360手機助手等都使用SlidingMenu的界面方案。2016-05-05
android studio 新手入門教程(三)Github( ignore忽略規(guī)則)的使用教程圖解
這篇文章主要介紹了android studio 新手入門教程(三)Github( ignore忽略規(guī)則)的使用教程圖解,需要的朋友可以參考下2017-12-12
Android 深入探究自定義view之流式布局FlowLayout的使用
FlowLayout(int align, int hgap, int vgap)創(chuàng)建一個新的流布局管理器,它具有指定的對齊方式以及指定的水平和垂直間隙,意思就是說從左上角開始添加原件,依次往后排,第一行擠滿了就換一行接著排2021-11-11
Android中獲取網(wǎng)頁表單中的數(shù)據(jù)實現(xiàn)思路及代碼
在Android中獲取網(wǎng)頁里表單中的數(shù)據(jù)具體實現(xiàn)代碼如下,感興趣的各位可以參考過下哈,希望對大家有所幫助2013-06-06

