中文字幕av专区_日韩电影在线播放_精品国产精品久久一区免费式_av在线免费观看网站

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Kotlin掛起函數原理是什么

發布時間:2022-08-04 14:10:01 來源:億速云 閱讀:201 作者:iii 欄目:開發技術

今天小編給大家分享一下Kotlin掛起函數原理是什么的相關知識點,內容詳細,邏輯清晰,相信大部分人都還太了解這方面的知識,所以分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后有所收獲,下面我們一起來了解一下吧。

    一、序言

    Kotlin掛起函數平時在學習和工作中用的比較多,掌握其原理還是很有必要的。

    ps: 文中所用的Kotlin版本是1.7.0。

    二、CPS原理

    在某個Kotlin函數的前面加個suspend函數,它就成了掛起函數(雖然內部不一定會掛起,內部不掛起的稱為偽掛起函數)。

    先隨便寫個掛起函數

    suspend fun getUserName(): String {
        delay(1000L)
        return "云天明"
    }

    然后通過Android Studio的Tools->Kotlin->Show Kotlin Bytecode->Decompile,現在我們拿到了Kotlin字節碼反編譯之后的Java代碼:

    public static final Object getUserName(@NotNull Continuation var0) {
        ...
    }

    可以看到該函數被編譯之后,多了一個Continuation參數,其次,返回值變成了Object。下面,我們詳細來討論一下這2種變化:函數參數和函數返回值。

    CPS參數變化

    上面的suspend fun getUserName(): String函數,如果我在Java中調用的話,會看到Android Studio提示我們

    Kotlin掛起函數原理是什么

    從圖中可以看到,新增了一個參數,也就是Continuation,它其實是一個Callback,只是換了個名字而已。

    來看下它的定義:

    /**
     * Interface representing a continuation after a suspension point that returns a value of type `T`.
     */
    @SinceKotlin("1.3")
    public interface Continuation<in T> {
        /**
         * 當前continuation所在協程的上下文
         */
        public val context: CoroutineContext
        /**
         * 繼續執行后面的協程代碼,同時把結果回調出去,結果可能是成功或失敗
         */
        public fun resumeWith(result: Result<T>)
    }

    這個Callback接口會在resumeWith回調結果給外部。

    CPS返回值變化

    在上面的Continuation接口的定義中,其實還有個小細節,它帶了個泛型T。這個泛型T就是我們suspend函數返回值的類型,上面的getUserName返回值是String,編譯之后,這個String就來到了Continuation的泛型中。

    而getUserName編譯之后的返回值變成了Object。為啥是Object?它有什么用?這個返回值其實是用來標識該函數是否掛起的標志,如果返回值是Intrinsics.COROUTINE_SUSPENDED,那么說明該函數被掛起了(掛起函數的結果不是通過函數返回值來獲取的,而是通過Continuation,也就是Callback回調得到的結果)。

    如果該函數是偽掛起函數(里面沒有其他掛起函數,但還是會進行CPS轉換),則是直接返回結果。

    舉個例子,下面這個就是真正的掛起函數:

    suspend fun getUserName(): String {
        delay(1000L)
        return "云天明"
    }

    當執行到delay的時候,就會返回Intrinsics.COROUTINE_SUSPENDED表示該函數被掛起了。

    下面這個則是偽掛起函數:

    suspend fun getName():String {
        return "程心"
    }

    這種偽掛起函數不會返回Intrinsics.COROUTINE_SUSPENDED,而是直接返回結果,它不會被掛起。它看起來就僅僅是一個普通函數,但還是會進行CPS轉換,CPS轉換只認suspend關鍵字。你如果像上面這樣寫,其實Android Studio也會提示你,說這個suspend關鍵字沒用,叫你把它移除掉。

    所以,suspend函數編譯之后的返回值變成了Object,因為要兼容偽掛起函數的返回值,而偽掛起函數可能返回任何值,而且還可能為空。

    下面我們就來詳細的探索一下掛起函數的底層原理,看看掛起函數反編譯之后是什么樣子。

    三、掛起函數的反編譯

    我們先寫個很簡單的suspend函數,然后將其反編譯,然后分析一下。具體的流程是我們用Android Studio寫個掛起函數的demo,然后編譯成apk,然后將apk用jadx反編譯一下,拿到對應class的反編譯Java源碼,這樣弄出來的源碼我感覺比直接通過Android Studio的Tools->Kotlin->Show Kotlin拿到的源碼稍微好看懂一些。

    首先,我創建了一個CpsTest.kt文件,然后在里面寫了一個函數:

    package com.xfhy.coroutine
    import kotlinx.coroutines.delay
    suspend fun getUserName(): String {
        delay(1000L)
        return "云天明"
    }

    就這樣,一個很普通的掛起函數,在內部只是簡單調用了下delay,延遲1000L,再返回結果“云天明”。雖然這個函數很簡單,但反編譯出來的代碼卻有點多,而且不好看懂,我先把原代碼貼出來,待會兒再放我重新組織過的代碼,作為對比:

    public final class CpsTestKt {
       @Nullable
       public static final Object getUserName(@NotNull Continuation var0) {
          Object $continuation;
          label20: {
             if (var0 instanceof <undefinedtype>) {
                $continuation = (<undefinedtype>)var0;
                if ((((<undefinedtype>)$continuation).label & Integer.MIN_VALUE) != 0) {
                   ((<undefinedtype>)$continuation).label -= Integer.MIN_VALUE;
                   break label20;
                }
             }
             $continuation = new ContinuationImpl(var0) {
                // $FF: synthetic field
                Object result;
                int label;
                @Nullable
                public final Object invokeSuspend(@NotNull Object $result) {
                   this.result = $result;
                   this.label |= Integer.MIN_VALUE;
                   return CpsTestKt.getUserName(this);
                }
             };
          }
          Object $result = ((<undefinedtype>)$continuation).result;
          Object var3 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
          switch(((<undefinedtype>)$continuation).label) {
          case 0:
             ResultKt.throwOnFailure($result);
             ((<undefinedtype>)$continuation).label = 1;
             if (DelayKt.delay(1000L, (Continuation)$continuation) == var3) {
                return var3;
             }
             break;
          case 1:
             ResultKt.throwOnFailure($result);
             break;
          default:
             throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
          }
          return "云天明";
       }
    }

    這反編譯之后的東西不太好看懂,我重新組織了一下:

    public final class CpsTestKt {
        public static final Object getUserName(Continuation<? super java.lang.String> continuation) {
            //這個TestContinuation實質上是一個匿名內部類,這里給它取個名字而已
            final class TestContinuation extends ContinuationImpl {
                //協程狀態機當前的狀態
                int label;
                //保存invokeSuspend回調時吐出來的返回結果
                Object result;
                TestContinuation(Continuation continuation) {
                    super(continuation);
                }
                //invokeSuspend比較重要,它是狀態機的入口,會將執行流程交給getUserName再次調用
                //協程的本質,就是CPS+狀態機
                public final Object invokeSuspend(Object obj) {
                    //callback回調時會把結果帶出來
                    this.result = obj;
                    this.label |= Integer.MIN_VALUE;
                    //開啟協程狀態機
                    return CpsTestKt.getUserName(this);
                }
            }
            TestContinuation testContinuation;
            label20:
            {
                //不是第一次進入,則走這里,把continuation轉成TestContinuation,TestContinuation只會生成一個實例,不會每次都生成。
                if (continuation instanceof TestContinuation) {
                    testContinuation = (TestContinuation) continuation;
                    if ((testContinuation.label & Integer.MIN_VALUE) != 0) {
                        testContinuation.label -= Integer.MIN_VALUE;
                        break label20;
                    }
                }
                //如果是第一次進入getUserName,則TestContinuation還沒被創建,會走到這里,此時先去創建一個TestContinuation
                testContinuation = new TestContinuation(continuation);
            }
            //將之前執行的結果取出來
            Object $result = testContinuation.result;
            //掛起的標志,如果需要掛起的話,就返回這個flag
            Object flag = IntrinsicsKt.getCOROUTINE_SUSPENDED();
            //狀態機
            switch (testContinuation.label) {
                case 0:
                    // 檢測異常
                    ResultKt.throwOnFailure($result);
                    //將label的狀態改成1,方便待會兒執行delay后面的代碼
                    testContinuation.label = 1;
                    //0. 調用DelayKt.delay函數
                    //1. 將testContinuation傳了進去
                    //2. DelayKt.delay是一個掛起函數,正常情況下,它會立馬返回一個值:IntrinsicsKt.COROUTINE_SUSPENDED(也就是這里的flag),表示該函數已被掛起,這里就直接return了,該函數被掛起
                    //3. 恢復執行:在DelayKt.delay內部,到了指定的時間后就會調用testContinuation這個Callback的invokeSuspend
                    //4. invokeSuspend中又將執行getUserName函數,同時將之前創建好的testContinuation傳入其中,開始執行后面的邏輯(label為1的邏輯),該函數繼續往后面執行(也就是恢復執行)
                    if (DelayKt.delay(1000L, testContinuation) == flag) {
                        return flag;
                    }
                    break;
                case 1:
                    // 檢測異常
                    ResultKt.throwOnFailure($result);
                    //label 1這里沒有return,而是會走到下面的return "云天明"語句
                    break;
                default:
                    throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
            }
            return "云天明";
        }
    }

    在getUserName函數中,會多出一個ContinuationImpl的子類,它是一個匿名內部類(為了方便,給它取了個名字TestContinuation),也是整個協程掛起函數的核心。在這個TestContinuation中有2個變量

    • label: 協程狀態機當前的狀態

    • result: 保存invokeSuspend回調時吐出來的返回結果

    invokeSuspend是一個抽象方法,當協程從掛起狀態想要恢復時,就得調用這個invokeSuspend,然后繼續走狀態機邏輯,繼續執行后面的代碼。具體是怎么調用這個invokeSuspend的,后面有機會再細說。暫時我們只要知道,這里是恢復的入口就行。invokeSuspend內部會把結果(這個結果可能是正常的結果,也可能是Exception)取出來,開啟協程狀態機。

    分析完TestContinuation,再來看一下第一次進入getUserName是怎么走的。

    • 首先,第一次進入時,continuation肯定不是TestContinuation,因為此時還沒有new過TestContinuation實例,所以會走到創建TestContinuation的邏輯,并且會把continuation包進去。

    • 然后剛創建完的testContinuation的label未賦其他值,那就是初始值0了。那么switch狀態機那里,就走case 0,先把label改成1,因為馬上就要掛起了,待會兒恢復時需要執行下一個狀態的代碼。

    • 調用Kotlin的庫函數delay,它是一個掛起函數,將testContinuation傳入其中,方便它進行invokeSuspend回調。調用掛起函數,那么它可能會返回COROUTINE_SUSPENDED,表示它已經被掛起了,如果是掛起了那么getUserName就走完了,到時會從invokeSuspend恢復。在還沒有恢復的時候,這個協程所在的線程可以去做其他事情。

    恢復的時候,又開始從頭走getUserName,此時的continuation已經是TestContinuation,不會重新創建。它的label之前已經被改成1了的,所以switch狀態機那里,會走到case 1,先檢測一下有沒有異常,沒有異常就返回真正的返回值了“云天明”。

    分析到這里也就完了,上面就是一個非常簡單的掛起函數的反編譯分析的整個過程。下面我們簡單分析一下偽掛起函數會帶來什么效果。

    四、偽掛起函數

    在之前的CpsTest.kt里面簡單改一下

    suspend fun fakeSuspendFun() = "維德"
    suspend fun getUserName(): String {
        println(fakeSuspendFun())
        return "云天明"
    }

    像fakeSuspendFun這種就是偽掛起函數,平時不建議像fakeSuspendFun這么寫,即使寫了,Android Studio也會提示你,這suspend關鍵字沒用,內部沒有掛起。它內部沒有掛起的邏輯,但是它有suspend關鍵字,那么Kotlin編譯器依然會給它做CPS轉換。

    public final class CpsTestKt {
       @Nullable
       public static final Object fakeSuspendFun(@NotNull Continuation<? super java.lang.String> $completion) {
          return "維德";
       }
       @Nullable
       public static final Object getUserName(@NotNull Continuation<? super java.lang.String> continuation) {
        final class TestContinuation extends ContinuationImpl {
            int label;
            Object result;
            TestContinuation(Continuation continuation) {
                super(continuation);
            }
            public final Object invokeSuspend(Object obj) {
                this.result = obj;
                this.label |= Integer.MIN_VALUE;
                return CpsTestKt.getUserName(this);
            }
        }
        TestContinuation testContinuation;
        label20:
        {
            if (continuation instanceof TestContinuation) {
                testContinuation = (TestContinuation) continuation;
                if ((testContinuation.label & Integer.MIN_VALUE) != 0) {
                    testContinuation.label -= Integer.MIN_VALUE;
                    break label20;
                }
            }
            testContinuation = new TestContinuation(continuation);
        }
          Object $result = testContinuation.result;
          Object flag = IntrinsicsKt.getCOROUTINE_SUSPENDED();
          //變化在這里,這個變量用來存儲fakeSuspendFun的返回值
          Object var10000;
          switch(testContinuation.label) {
          case 0:
             ResultKt.throwOnFailure($result);
             testContinuation.label = 1;
             var10000 = fakeSuspendFun((Continuation)$continuation);
             if (var10000 == flag) {
                //如果是掛起,那么直接返回COROUTINE_SUSPENDED
                return flag;
             }
             //顯然,這里是不會掛起的,會走這里的break
             break;
          case 1:
             ResultKt.throwOnFailure($result);
             var10000 = $result;
             break;
          default:
             throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
          }
          //走這里
          Object var1 = var10000;
          System.out.println(var1);
          return "云天明";
       }
    }

    在調用偽掛起函數時,不會掛起,它不會返回COROUTINE_SUSPENDED,而是繼續往下走。

    五、多個掛起函數前后關聯

    平時在工作中,可能經常會有多個掛起函數前后是關聯的,后面一個掛起函數需要前面一個掛起函數的結果來干點事情,比上面只有一個getUserName掛起函數稍微復雜些,我們來分析一下。

    比如我們拿到一個需求,展示我的朋友圈,假設獲取流程如下:獲取用戶id->根據用戶id獲取該用戶的好友列表->獲取好友列表每個人的朋友圈。下面是非常簡單的實現:

    //需求: 獲取用戶id->根據用戶id獲取該用戶的好友列表->獲取好友列表每個人的朋友圈
    suspend fun showMoments() {
        println("start")
        val userId = getUserId()
        println(userId)
        val friendList = getFriendList(userId)
        println(friendList)
        val feedList = getFeedList(userId, friendList)
        println(feedList)
    }
    suspend fun getUserId(): String {
        delay(1000L)
        return "1sa13124daadar2"
    }
    suspend fun getFriendList(userId: String): String {
        println("正在獲取${userId}的朋友列表")
        delay(1000L)
        return "云天明, 維德"
    }
    suspend fun getFeedList(userId: String, list: String): String {
        println("獲取${userId}的朋友圈($list)")
        delay(1000L)
        return "云天明: 酒好喝嗎?煙好抽嗎?即使是可口可樂,第一次嘗也不好喝,讓人上癮的東西都是這樣;\n維德: 前進!前進!!不擇手段地前進!!!"
    }

    它的執行結果如下:

    start
    1sa13124daadar2
    正在獲取1sa13124daadar2的朋友列表
    云天明, 維德
    獲取1sa13124daadar2的朋友圈(云天明, 維德)
    云天明: 酒好喝嗎?煙好抽嗎?即使是可口可樂,第一次嘗也不好喝,讓人上癮的東西都是這樣;
    維德: 前進!前進!!不擇手段地前進!!!
    end

    這段代碼要稍微復雜一些,這些掛起函數前后關聯,前面獲取到的數據后面的掛起函數需要使用到。相應的,它們反編譯之后也要復雜一些。但是沒關系,我已經把晦澀難懂的代碼重新組裝了一下,方便大家閱讀。同時,在下面的代碼中,每一步在走哪個分支,都有詳細的注釋分析,幫助大家理解。

    public final class TestSuspendKt {
       @Nullable
       public static final Object showMoments(@NotNull Continuation<? super Unit> continuation) {
          ShowMomentsContinuation showMomentsContinuation;
          label37: {
             if (continuation instanceof ShowMomentsContinuation) {
                //非第一次進showMoments,則走這里,continuation已經是ShowMomentsContinuation了
                showMomentsContinuation = (ShowMomentsContinuation)continuation;
                if ((showMomentsContinuation.label & Integer.MIN_VALUE) != 0) {
                   showMomentsContinuation.label -= Integer.MIN_VALUE;
                   break label37;
                }
             }
             //第一次,走這里,初始化ShowMomentsContinuation,將傳入的continuation包起來
             showMomentsContinuation = new ShowMomentsContinuation(continuation);
             final class ShowMomentsContinuation extends ContinuationImpl {
                int label;
                Object result;
                //存放臨時數據
                Object tempData;
                ShowMomentsContinuation(Continuation continuation) {
                    super(continuation);
                }
                public final Object invokeSuspend(Object obj) {
                    this.result = obj;
                    this.label |= Integer.MIN_VALUE;
                    return CpsTestKt.getUserName(this);
                }
            }
          }
          //存放每個函數的返回結果,臨時放一下
          Object computeResult;
          label31: {
             String userId;
             Object flag;
             label30: {
                //從continuation中把result取出來
                Object $result = showMomentsContinuation.result;
                flag = IntrinsicsKt.getCOROUTINE_SUSPENDED();
                switch(showMomentsContinuation.label) {
                case 0:
                   //第一次,走這里,檢測異常
                   ResultKt.throwOnFailure($result);
                   System.out.println("start");
                   //將label改成1
                   showMomentsContinuation.label = 1;
                   //執行getUserId函數,computeResult用來接收返回值
                   computeResult = getUserId((Continuation)showMomentsContinuation);
                   //getUserId是掛起函數,不出意外的話,computeResult的值會是COROUTINE_SUSPENDED,這里就直接return了
                   //showMoments函數這一次執行,就算完成了。
                   //恢復執行時,會走ShowMomentsContinuation 的invokeSuspend,走下面label等于1的邏輯
                   if (computeResult == flag) {
                      return flag;
                   }
                   break;
                case 1:
                   //第二次執行showMoments時,label已經等于1了,走這里. 
                   ResultKt.throwOnFailure($result);
                   computeResult = $result;
                   break;
                case 2:
                   //第三次執行showMoments時,label已經等于2了,走這里. 
                   //先將之前暫存的userId取出來,馬上需要用到
                   userId = (String)showMomentsContinuation.tempData;
                   ResultKt.throwOnFailure($result);
                   computeResult = $result;
                   break label30;
                case 3:
                   //第四次執行showMoments時,label已經等于3了,走這里. 
                   ResultKt.throwOnFailure($result);
                   computeResult = $result;
                   break label31;
                default:
                   throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
                }
                //第二次執行showMoments時,label=1,會走到這里來,將getUserId函數回調回來的userId保存起來,并輸出
                userId = (String)computeResult;
                System.out.println(userId);
                //將userId放continuation里面暫存起來
                showMomentsContinuation.tempData = userId;
                //又要執行掛起函數了,這里將label改成2
                showMomentsContinuation.label = 2;
                //開始調用getFriendList
                computeResult = getFriendList(userId, (Continuation)showMomentsContinuation);
                //getFriendList是掛起函數,不出意外的話,computeResult的值會是COROUTINE_SUSPENDED,這里就直接return了
                //showMoments函數這一次執行,就算完成了。
                //恢復執行時,會走ShowMomentsContinuation 的invokeSuspend,走上面label等于2的邏輯
                if (computeResult == flag) {
                   return flag;
                }
             }
             //第三次執行showMoments時,label=2,會走到這里來,將getFriendList函數回調回來的friendList輸出
             String friendList = (String)computeResult;
             System.out.println(friendList);
             showMomentsContinuation.tempData = null;
             //又要執行掛起函數了,這里將label改成3
             showMomentsContinuation.label = 3;
             //開始調用getFeedList
             computeResult = getFeedList(userId, friendList, (Continuation)showMomentsContinuation);
             //getFeedList是掛起函數,不出意外的話,computeResult的值會是COROUTINE_SUSPENDED,這里就直接return了
             //showMoments函數這一次執行,就算完成了。
             //恢復執行時,會走ShowMomentsContinuation 的invokeSuspend,走上面label等于3的邏輯
             if (computeResult == flag) {
                return flag;
             }
          }
          //第四次執行showMoments時,label=3,會走到這里來,將getFeedList函數回調回來的feedList輸出
          String feedList = (String)computeResult;
          System.out.println(feedList);
          System.out.println("end");
          //showMoments函數這一次執行,就算完成了。
          //沒有剩下的掛起函數需要執行了
          return Unit.INSTANCE;
       }
        //因為getUserId、getFriendList、getFeedList中的匿名內部類邏輯與showMoments中的一模一樣,故沒有將其重新組織語言
       @Nullable
       public static final Object getUserId(@NotNull Continuation var0) {
          Object $continuation;
          label20: {
            //這里的<undefinedtype>就是在getUserId函數里生成的new ContinuationImpl匿名內部類
             if (var0 instanceof <undefinedtype>) {
                $continuation = (<undefinedtype>)var0;
                if ((((<undefinedtype>)$continuation).label & Integer.MIN_VALUE) != 0) {
                   ((<undefinedtype>)$continuation).label -= Integer.MIN_VALUE;
                   break label20;
                }
             }
             $continuation = new ContinuationImpl(var0) {
                // $FF: synthetic field
                Object result;
                int label;
                @Nullable
                public final Object invokeSuspend(@NotNull Object $result) {
                   this.result = $result;
                   this.label |= Integer.MIN_VALUE;
                   return TestSuspendKt.getUserId(this);
                }
             };
          }
          Object $result = ((<undefinedtype>)$continuation).result;
          Object var3 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
          switch(((<undefinedtype>)$continuation).label) {
          case 0:
             //第一次執行getUserId時,走這里
             ResultKt.throwOnFailure($result);
             //馬上要開始執行掛起函數了,label先改一下
             ((<undefinedtype>)$continuation).label = 1;
             //執行delay,正常情況下,會返回COROUTINE_SUSPENDED,于是getUserId這一次就執行完了,return了
             //恢復時會回調上面的匿名內部類$continuation中的invokeSuspend
             if (DelayKt.delay(1000L, (Continuation)$continuation) == var3) {
                return var3;
             }
             break;
          case 1:
             //第二次執行getUserId時,也就是delay執行完回來,走這里
             ResultKt.throwOnFailure($result);
             break;
          default:
             throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
          }
          //拿到數據,getUserId就算真正的執行完了,接著會去執行showMoments函數中的ShowMomentsContinuation#invokeSuspend,也就是恢復showMoments,繼續執行showMoments中getUserId后面的邏輯
          return "1sa13124daadar2";
       }
       @Nullable
       public static final Object getFriendList(@NotNull String userId, @NotNull Continuation var1) {
          Object $continuation;
          label20: {
             if (var1 instanceof <undefinedtype>) {
                $continuation = (<undefinedtype>)var1;
                if ((((<undefinedtype>)$continuation).label & Integer.MIN_VALUE) != 0) {
                   ((<undefinedtype>)$continuation).label -= Integer.MIN_VALUE;
                   break label20;
                }
             }
             $continuation = new ContinuationImpl(var1) {
                // $FF: synthetic field
                Object result;
                int label;
                @Nullable
                public final Object invokeSuspend(@NotNull Object $result) {
                   this.result = $result;
                   this.label |= Integer.MIN_VALUE;
                   return TestSuspendKt.getFriendList((String)null, this);
                }
             };
          }
          Object $result = ((<undefinedtype>)$continuation).result;
          Object var5 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
          switch(((<undefinedtype>)$continuation).label) {
          case 0:
             ResultKt.throwOnFailure($result);
             String var2 = "正在獲取" + userId + "的朋友列表";
             System.out.println(var2);
             ((<undefinedtype>)$continuation).label = 1;
             if (DelayKt.delay(1000L, (Continuation)$continuation) == var5) {
                return var5;
             }
             break;
          case 1:
             ResultKt.throwOnFailure($result);
             break;
          default:
             throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
          }
          return "云天明, 維德";
       }
       @Nullable
       public static final Object getFeedList(@NotNull String userId, @NotNull String list, @NotNull Continuation var2) {
          Object $continuation;
          label20: {
             if (var2 instanceof <undefinedtype>) {
                $continuation = (<undefinedtype>)var2;
                if ((((<undefinedtype>)$continuation).label & Integer.MIN_VALUE) != 0) {
                   ((<undefinedtype>)$continuation).label -= Integer.MIN_VALUE;
                   break label20;
                }
             }
             $continuation = new ContinuationImpl(var2) {
                // $FF: synthetic field
                Object result;
                int label;
                @Nullable
                public final Object invokeSuspend(@NotNull Object $result) {
                   this.result = $result;
                   this.label |= Integer.MIN_VALUE;
                   return TestSuspendKt.getFeedList((String)null, (String)null, this);
                }
             };
          }
          Object $result = ((<undefinedtype>)$continuation).result;
          Object var6 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
          switch(((<undefinedtype>)$continuation).label) {
          case 0:
             ResultKt.throwOnFailure($result);
             String var3 = "獲取" + userId + "的朋友圈(" + list + ')';
             System.out.println(var3);
             ((<undefinedtype>)$continuation).label = 1;
             if (DelayKt.delay(1000L, (Continuation)$continuation) == var6) {
                return var6;
             }
             break;
          case 1:
             ResultKt.throwOnFailure($result);
             break;
          default:
             throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
          }
          return "云天明: 酒好喝嗎?煙好抽嗎?即使是可口可樂,第一次嘗也不好喝,讓人上癮的東西都是這樣;\n維德: 前進!前進!!不擇手段地前進!!!";
       }
    }

    觀察源碼,發現一些東西:

    • 每個掛起函數都有一個匿名內部類,繼承ContinuationImpl,在invokeSuspend中開啟狀態機

    • 每個掛起函數都經過了CPS轉換

    • 在掛起之后,當前執行協程的這個線程其實是空閑的,沒有代碼交給它執行。在invokeSuspend恢復之后,才繼續執行

    • 每個掛起函數,都有一個狀態機

    • 掛起函數中的邏輯被分塊執行(也就是狀態機那塊的邏輯),分塊的數量=掛起函數數量+1

    基本上來說,掛起函數的實現原理就是上面這些了。

    六、在Java中調用suspend函數

    既然Kotlin是兼容Java的,那么如果我想在Java里面調用Kotlin的suspend函數按道理也是可以的。那具體如何調用呢?

    就拿上面的案例舉例,假設我想在Activity中點擊某個按鈕時調用showMoments這個suspend函數,該怎么搞?大家先思考一下,稍后給出答案。

    //將上面的案例加了個返回值
    suspend fun showMoments(): String {
        println("start")
        val userId = getUserId()
        println(userId)
        val friendList = getFriendList(userId)
        println(friendList)
        val feedList = getFeedList(userId, friendList)
        println(feedList)
        println("end")
        return feedList
    }

    因為showMoments函數有suspend關鍵字,那么最終會經過CPS轉換,有一個Continuation參數。在Java中調用showMoments時,肯定需要把Continuation傳進去才行。Continuation是一個接口,需要傳個實現類過去,把getContext和resumeWith實現起。

    TestSuspendKt.showMoments(new Continuation<String>() {
        @NonNull
        @Override
        public CoroutineContext getContext() {
            return (CoroutineContext) Dispatchers.getIO();
        }
        @Override
        public void resumeWith(@NonNull Object result) {
            //這里的result就是showMoments的返回值
            Log.d("xfhy666", "" + result);
        }
    });

    Java中調用掛起函數,看起來就像是調用了一個方法,這個方法需要傳一個callback過去,這個方法的返回值是通過回調給出來的,并且可以自定義該方法運行在哪個線程中。

    以上就是“Kotlin掛起函數原理是什么”這篇文章的所有內容,感謝各位的閱讀!相信大家閱讀完這篇文章都有很大的收獲,小編每天都會為大家更新不同的知識,如果還想學習更多的知識,請關注億速云行業資訊頻道。

    向AI問一下細節

    免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

    AI

    锡林浩特市| 公主岭市| 威宁| 民权县| 平远县| 建宁县| 秭归县| 徐州市| 绥德县| 缙云县| 江孜县| 临海市| 尼玛县| 藁城市| 随州市| 麟游县| 泗洪县| 宿迁市| 依兰县| 汶上县| 云梦县| 崇阳县| 京山县| 江西省| 栾城县| 日土县| 广昌县| 沁源县| 黔江区| 灵宝市| 朔州市| 柳州市| 岢岚县| 岑溪市| 彰化县| 东源县| 北辰区| 临武县| 林芝县| 建宁县| 绵阳市|