文章目錄
-
-
-
-
- 前言
- 疑問1.為啥用協程還要添加一個額外的依賴庫 太奇怪了
- 我的runBlocking,launch,withContext 等等都哪去了?
-
- 跟蹤到标準庫去看看
- 從現在開始進入到标準庫了
- 下個小結論
- this is BaseContinuationImpl 可能為true嗎
- 回到問題:為啥用協程還要添加一個額外的依賴庫 不加行不行
-
-
-
前言
前幾天開始正式學習研究coroutines了 因為之前一直處于會用的狀态很多東西都是懵懂的
以下研究基于idea環境
疑問1.為啥用協程還要添加一個額外的依賴庫 太奇怪了
// https://mvnrepository.com/artifact/org.jetbrains.kotlinx/kotlinx-coroutines-core
implementation group: 'org.jetbrains.kotlinx', name: 'kotlinx-coroutines-core', version: '1.5.1'
suspend關鍵字不是kotlin裡面提供的嗎 那麼我不加依賴行不行 ?帶着這個疑問我開始了研究
我的runBlocking,launch,withContext 等等都哪去了?
為了友善對照 我又開了新的kotlin application項目加上以上的依賴 最終發現 那些方法都是 kotlinx.coroutines 包裡面的
這和我的預期完全不符
提供了關鍵字但是标準庫又不做任何底層支援 這實在太奇怪了
跟蹤到标準庫去看看
标準庫也不是什麼都沒有 它裡面定義了以下(按照繼承關系進行了對齊)
- CoroutineContext
- Element
- ContinuationInterceptor
- AbstractCoroutineContextElement
- CombinedContext
- Element
- Key
- Continuation
那麼問題來了:是以協程他是kotlin标準庫定義了一部分 然後又用其他庫來拓展api嗎?
我帶着這個問題找了個最簡單的api launch
我們傳入了一個 方法 然後他就把這個方法 start 起來了
跟蹤到start方法
我相信絕大多數人都不例外 看得懵懵的
又是泛型,又是帶泛型的函數入參
而且最坑的是想要繼續跟蹤裡面的start方法 一點選還又給你指向到原地
正确的做法是點選那個括号 如下圖 windows 上 ctrl+滑鼠左鍵
因為這是一個操作符重載
在這個invoke方法中 這個this就是 之前 launch方法中傳入的start對象 他是有個預設值的
是以他會走到 block.startCoroutineCancellable(receiver, completion) 的分支 這個方法他是一個函數的拓展,需要提及的一下就是直到這裡這裡仍然還是 kotlinx中的協程的api
runSafely目前來講不用管 它就是再包了一層 做了個異常處理 我們進入到createCoroutineUnintercepted 方法中 進入到這個方法還有點複雜 我們稍後再細說
到了這裡來小結一下:
- 調用launch方法 裡面構造了一個 StandaloneCoroutine或者LazyStandaloneCoroutine(StandaloneCoroutine的子類)
- 然後調用其start方法 這個start方法定義在他的父類之中 AbstractCoroutine
- 在這個start方法中 調用了 CoroutineStart的操作符重載的start方法
- 在這個操作符方法(start)裡面調用了 一個suspend函數的拓展方法 startCoroutineCancellable
- 在startCoroutineCancellable中
createCoroutineUnintercepted(receiver, completion).intercepted().resumeCancellableWith(Result.success(Unit), onCancellation)
從現在開始進入到标準庫了
createCoroutineUnintercepted方法的定義在kotlin.coroutines.intrinsics.IntrinsicsJvm.kt:118中
代碼很簡單啊 根據類型 分了兩個分支邏輯 但是我們好好想一下 這個this是什麼 它就是我們的block啊
那麼這個代碼為什麼要這樣寫呢 if( block is BaseContinuationImpl )
這不可能為true啊!!! 難道說它在什麼地方做了什麼奇怪的魔術嗎
點選這裡檢視為true的分支
那我們就先看看false的分支
在這個方法中他也是會傳回 BaseContinuationImpl 的執行個體對象 如下圖 未折疊版大圖看這裡
這個回調就會随後在其 BaseContinuationImpl 的執行個體對象 的invokeSuspend方法中調用
下個小結論
我們的block被強制轉換成了Function2然後執行了invoke方法 整個流程就完成了
launch-> StandaloneCoroutine::start -> CoroutineStart::start -> block::startCoroutineCancellable -> block::createCoroutineUnintercepted ->
block強制轉換成 Function2然後 invoke
好 那麼問題來了它把this (也就是block: suspend CoroutineScope.() -> Unit) 強制轉換成了 Fuction2
黑人問号臉???
我記得他的方法簽名是 suspend CoroutineScope.() ->Unit 換算一下就是
suspend T.() -> T2
而Function2的簽名為 Fuction<T1,T2,T3>換算成lamda就是 T1.(T2) -> T3
suspend T.() -> T2
vs
T1.(T2) -> T3
你确定這能轉換嗎
做個小demo 定義一個suspend方法的的函數對象 然後反編譯看看
咱們定義的還真被編譯成了Fuction2 而且還給我們添加幾個額外的方法 invokeSuspend和create ,invoke方法是對Function2的實作 是以上面的強制轉換是合理的
suspend 方法反編譯的代碼:
檢視false分支
this is BaseContinuationImpl 可能為true嗎
我們在此處打個斷點調試看看
在控制台分别調用 來檢視他的類結構
this.javaClass.superclass
this.javaClass.interfaces
得到結果是:
this.javaClass.superclass => class kotlin.coroutines.jvm.internal.SuspendLambda
this.javaClass.interfaces => interface kotlin.jvm.functions.Function2
interfaces的結果我們能夠了解 因為在false的邏輯中我們也已經證明這一點
但是他的父類是 SuspendLambda 我就不能了解了 難道說 supsend修飾的高階函數是隐形中預設繼承了SuspendLambda的嗎 (關于這一點我目前還無法證明)如果是的話 那麼一切就都說得通了
它調用自己的 create方法
而這個create方法在哪裡呢 就在 這兒
回到問題:為啥用協程還要添加一個額外的依賴庫 不加行不行
通過之前的分析 我們知道了 協程是通過一個特殊的 拓展方法建立啟動的 而這個方法是定義在标準庫的
為啥用協程還要添加一個額外的依賴庫 不加行不行?
那麼結論就是 當然可以了 我們自己調用這個方法不就行了嗎
這個方法它間接的調用了createCoroutineUnintercepted方法
本文完。