天天看點

coroutines 學習随筆「一」

文章目錄

          • 前言
        • 疑問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
  • Key
  • Continuation

那麼問題來了:是以協程他是kotlin标準庫定義了一部分 然後又用其他庫來拓展api嗎?

我帶着這個問題找了個最簡單的api launch

coroutines 學習随筆「一」

我們傳入了一個 方法 然後他就把這個方法 start 起來了

跟蹤到start方法

coroutines 學習随筆「一」

我相信絕大多數人都不例外 看得懵懵的

又是泛型,又是帶泛型的函數入參

而且最坑的是想要繼續跟蹤裡面的start方法 一點選還又給你指向到原地

正确的做法是點選那個括号 如下圖 windows 上 ctrl+滑鼠左鍵

coroutines 學習随筆「一」

因為這是一個操作符重載

coroutines 學習随筆「一」

在這個invoke方法中 這個this就是 之前 launch方法中傳入的start對象 他是有個預設值的

coroutines 學習随筆「一」

是以他會走到 block.startCoroutineCancellable(receiver, completion) 的分支 這個方法他是一個函數的拓展,需要提及的一下就是直到這裡這裡仍然還是 kotlinx中的協程的api

coroutines 學習随筆「一」

runSafely目前來講不用管 它就是再包了一層 做了個異常處理 我們進入到createCoroutineUnintercepted 方法中 進入到這個方法還有點複雜 我們稍後再細說

coroutines 學習随筆「一」

到了這裡來小結一下:

  1. 調用launch方法 裡面構造了一個 StandaloneCoroutine或者LazyStandaloneCoroutine(StandaloneCoroutine的子類)
  2. 然後調用其start方法 這個start方法定義在他的父類之中 AbstractCoroutine
  3. 在這個start方法中 調用了 CoroutineStart的操作符重載的start方法
  4. 在這個操作符方法(start)裡面調用了 一個suspend函數的拓展方法 startCoroutineCancellable
  5. 在startCoroutineCancellable中
createCoroutineUnintercepted(receiver, completion).intercepted().resumeCancellableWith(Result.success(Unit), onCancellation)
           
從現在開始進入到标準庫了

createCoroutineUnintercepted方法的定義在kotlin.coroutines.intrinsics.IntrinsicsJvm.kt:118中

coroutines 學習随筆「一」

代碼很簡單啊 根據類型 分了兩個分支邏輯 但是我們好好想一下 這個this是什麼 它就是我們的block啊

coroutines 學習随筆「一」

那麼這個代碼為什麼要這樣寫呢 if( block is BaseContinuationImpl )

這不可能為true啊!!! 難道說它在什麼地方做了什麼奇怪的魔術嗎

點選這裡檢視為true的分支

那我們就先看看false的分支

coroutines 學習随筆「一」

在這個方法中他也是會傳回 BaseContinuationImpl 的執行個體對象 如下圖 未折疊版大圖看這裡

coroutines 學習随筆「一」

這個回調就會随後在其 BaseContinuationImpl 的執行個體對象 的invokeSuspend方法中調用

coroutines 學習随筆「一」
下個小結論

我們的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
           

你确定這能轉換嗎

coroutines 學習随筆「一」

做個小demo 定義一個suspend方法的的函數對象 然後反編譯看看

咱們定義的還真被編譯成了Fuction2 而且還給我們添加幾個額外的方法 invokeSuspend和create ,invoke方法是對Function2的實作 是以上面的強制轉換是合理的

suspend 方法反編譯的代碼:

coroutines 學習随筆「一」

檢視false分支

this is BaseContinuationImpl 可能為true嗎

我們在此處打個斷點調試看看

coroutines 學習随筆「一」

在控制台分别調用 來檢視他的類結構

this.javaClass.superclass

this.javaClass.interfaces

coroutines 學習随筆「一」

得到結果是:

this.javaClass.superclass => class kotlin.coroutines.jvm.internal.SuspendLambda

this.javaClass.interfaces => interface kotlin.jvm.functions.Function2

interfaces的結果我們能夠了解 因為在false的邏輯中我們也已經證明這一點

但是他的父類是 SuspendLambda 我就不能了解了 難道說 supsend修飾的高階函數是隐形中預設繼承了SuspendLambda的嗎 (關于這一點我目前還無法證明)如果是的話 那麼一切就都說得通了

它調用自己的 create方法

coroutines 學習随筆「一」

而這個create方法在哪裡呢 就在 這兒

回到問題:為啥用協程還要添加一個額外的依賴庫 不加行不行

通過之前的分析 我們知道了 協程是通過一個特殊的 拓展方法建立啟動的 而這個方法是定義在标準庫的

coroutines 學習随筆「一」

為啥用協程還要添加一個額外的依賴庫 不加行不行?

那麼結論就是 當然可以了 我們自己調用這個方法不就行了嗎

這個方法它間接的調用了createCoroutineUnintercepted方法

coroutines 學習随筆「一」

本文完。