日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學(xué)無先后,達(dá)者為師

網(wǎng)站首頁 編程語言 正文

Kotlin掛起函數(shù)應(yīng)用介紹_Android

作者:且聽真言 ? 更新時間: 2022-12-21 編程語言

學(xué)習(xí)了極客時間課程,記錄下學(xué)習(xí)輸出。

一、CPS轉(zhuǎn)換

掛起函數(shù),比普通的函數(shù)多了 suspend 關(guān)鍵字。通過suspend 關(guān)鍵字,Kotlin 編譯器就會特殊對待這個函數(shù),將其轉(zhuǎn)換成一個帶有 Callback 的函數(shù),這里的 Callback 就是 Continuation 接口。

例、CPS 轉(zhuǎn)換:

suspend fun getUserInfo(): Any {
    return "UserInfo"
}
----->
fun getUserInfo(ct:Continuation): Any? {
    ct.resumeWith("UserInfo")
    return Unit
}

PS 轉(zhuǎn)換過程中,函數(shù)的類型發(fā)生了變化:suspend ()->Any 變成了 (Continuation)-> Any?。這意味著,如果你在 Java 里訪問一個 Kotlin 掛起函數(shù) getUserInfo(),會看到 getUserInfo() 的類型是 (Continuation)-> Object,接收 Continuation 為參數(shù),返回值是 Object。而在這里,函數(shù)簽名的變化可以分為兩個部分:函數(shù)簽名的變化可以分為兩個部分:函數(shù)參數(shù)的變化和函數(shù)返回值的變化。

1.CPS 參數(shù)變化

suspend() 變成 (Continuation)

suspend fun getUserInfoContent(): String {
    withContext(Dispatchers.IO) {
        delay(1000L)
    }
    return "UserInfo"
}
suspend fun getFriendListContent(user: String): String {
    withContext(Dispatchers.IO) {
        delay(1000L)
    }
    return "Friend1, Friend2"
}
suspend fun getFeedListContent(user: String, list: String): String {
    withContext(Dispatchers.IO) {
        delay(1000L)
    }
    return "{FeddList...}"
}
suspend fun fetchContent() {
    val userInfoContent = getUserInfoContent()
    val friendListContent = getFriendListContent(userInfoContent)
    val feedListContent = getFeedListContent(userInfoContent, friendListContent)
}

上述代碼轉(zhuǎn)換成java代碼如下:

public final class TestCoroutionKt {
   @Nullable
   public static final Object getUserInfoContent(@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 TestCoroutionKt.getUserInfoContent(this);
            }
         };
      }
      Object $result = ((<undefinedtype>)$continuation).result;
      Object var3 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
      switch(((<undefinedtype>)$continuation).label) {
      case 0:
         ResultKt.throwOnFailure($result);
         CoroutineContext var10000 = (CoroutineContext)Dispatchers.getIO();
         Function2 var10001 = (Function2)(new Function2((Continuation)null) {
            int label;
            @Nullable
            public final Object invokeSuspend(@NotNull Object $result) {
               Object var2 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
               switch(this.label) {
               case 0:
                  ResultKt.throwOnFailure($result);
                  this.label = 1;
                  if (DelayKt.delay(1000L, this) == var2) {
                     return var2;
                  }
                  break;
               case 1:
                  ResultKt.throwOnFailure($result);
                  break;
               default:
                  throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
               }
               return Unit.INSTANCE;
            }
            @NotNull
            public final Continuation create(@Nullable Object value, @NotNull Continuation completion) {
               Intrinsics.checkNotNullParameter(completion, "completion");
               Function2 var3 = new <anonymous constructor>(completion);
               return var3;
            }
            public final Object invoke(Object var1, Object var2) {
               return ((<undefinedtype>)this.create(var1, (Continuation)var2)).invokeSuspend(Unit.INSTANCE);
            }
         });
         ((<undefinedtype>)$continuation).label = 1;
         if (BuildersKt.withContext(var10000, var10001, (Continuation)$continuation) == var3) {
            return var3;
         }
         break;
      case 1:
         ResultKt.throwOnFailure($result);
         break;
      default:
         throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
      }
      return "UserInfo";
   }
   @Nullable
   public static final Object getFriendListContent(@NotNull String var0, @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 TestCoroutionKt.getFriendListContent((String)null, this);
            }
         };
      }
      Object $result = ((<undefinedtype>)$continuation).result;
      Object var4 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
      switch(((<undefinedtype>)$continuation).label) {
      case 0:
         ResultKt.throwOnFailure($result);
         CoroutineContext var10000 = (CoroutineContext)Dispatchers.getIO();
         Function2 var10001 = (Function2)(new Function2((Continuation)null) {
            int label;
            @Nullable
            public final Object invokeSuspend(@NotNull Object $result) {
               Object var2 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
               switch(this.label) {
               case 0:
                  ResultKt.throwOnFailure($result);
                  this.label = 1;
                  if (DelayKt.delay(1000L, this) == var2) {
                     return var2;
                  }
                  break;
               case 1:
                  ResultKt.throwOnFailure($result);
                  break;
               default:
                  throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
               }
               return Unit.INSTANCE;
            }
            @NotNull
            public final Continuation create(@Nullable Object value, @NotNull Continuation completion) {
               Intrinsics.checkNotNullParameter(completion, "completion");
               Function2 var3 = new <anonymous constructor>(completion);
               return var3;
            }
            public final Object invoke(Object var1, Object var2) {
               return ((<undefinedtype>)this.create(var1, (Continuation)var2)).invokeSuspend(Unit.INSTANCE);
            }
         });
         ((<undefinedtype>)$continuation).label = 1;
         if (BuildersKt.withContext(var10000, var10001, (Continuation)$continuation) == var4) {
            return var4;
         }
         break;
      case 1:
         ResultKt.throwOnFailure($result);
         break;
      default:
         throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
      }
      return "Friend1, Friend2";
   }
   @Nullable
   public static final Object getFeedListContent(@NotNull String var0, @NotNull String var1, @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 TestCoroutionKt.getFeedListContent((String)null, (String)null, this);
            }
         };
      }
      Object $result = ((<undefinedtype>)$continuation).result;
      Object var5 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
      switch(((<undefinedtype>)$continuation).label) {
      case 0:
         ResultKt.throwOnFailure($result);
         CoroutineContext var10000 = (CoroutineContext)Dispatchers.getIO();
         Function2 var10001 = (Function2)(new Function2((Continuation)null) {
            int label;
            @Nullable
            public final Object invokeSuspend(@NotNull Object $result) {
               Object var2 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
               switch(this.label) {
               case 0:
                  ResultKt.throwOnFailure($result);
                  this.label = 1;
                  if (DelayKt.delay(1000L, this) == var2) {
                     return var2;
                  }
                  break;
               case 1:
                  ResultKt.throwOnFailure($result);
                  break;
               default:
                  throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
               }
               return Unit.INSTANCE;
            }
            @NotNull
            public final Continuation create(@Nullable Object value, @NotNull Continuation completion) {
               Intrinsics.checkNotNullParameter(completion, "completion");
               Function2 var3 = new <anonymous constructor>(completion);
               return var3;
            }
            public final Object invoke(Object var1, Object var2) {
               return ((<undefinedtype>)this.create(var1, (Continuation)var2)).invokeSuspend(Unit.INSTANCE);
            }
         });
         ((<undefinedtype>)$continuation).label = 1;
         if (BuildersKt.withContext(var10000, var10001, (Continuation)$continuation) == var5) {
            return var5;
         }
         break;
      case 1:
         ResultKt.throwOnFailure($result);
         break;
      default:
         throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
      }
      return "{FeddList...}";
   }
   @Nullable
   public static final Object fetchContent(@NotNull Continuation var0) {
      Object $continuation;
      label37: {
         if (var0 instanceof <undefinedtype>) {
            $continuation = (<undefinedtype>)var0;
            if ((((<undefinedtype>)$continuation).label & Integer.MIN_VALUE) != 0) {
               ((<undefinedtype>)$continuation).label -= Integer.MIN_VALUE;
               break label37;
            }
         }
         $continuation = new ContinuationImpl(var0) {
            // $FF: synthetic field
            Object result;
            int label;
            Object L$0;
            @Nullable
            public final Object invokeSuspend(@NotNull Object $result) {
               this.result = $result;
               this.label |= Integer.MIN_VALUE;
               return TestCoroutionKt.fetchContent(this);
            }
         };
      }
      Object var10000;
      label31: {
         String userInfoContent;
         Object var6;
         label30: {
            Object $result = ((<undefinedtype>)$continuation).result;
            var6 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
            switch(((<undefinedtype>)$continuation).label) {
            case 0:
               ResultKt.throwOnFailure($result);
               ((<undefinedtype>)$continuation).label = 1;
               var10000 = getUserInfoContent((Continuation)$continuation);
               if (var10000 == var6) {
                  return var6;
               }
               break;
            case 1:
               ResultKt.throwOnFailure($result);
               var10000 = $result;
               break;
            case 2:
               userInfoContent = (String)((<undefinedtype>)$continuation).L$0;
               ResultKt.throwOnFailure($result);
               var10000 = $result;
               break label30;
            case 3:
               ResultKt.throwOnFailure($result);
               var10000 = $result;
               break label31;
            default:
               throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
            }
            userInfoContent = (String)var10000;
            ((<undefinedtype>)$continuation).L$0 = userInfoContent;
            ((<undefinedtype>)$continuation).label = 2;
            var10000 = getFriendListContent(userInfoContent, (Continuation)$continuation);
            if (var10000 == var6) {
               return var6;
            }
         }
         String friendListContent = (String)var10000;
         ((<undefinedtype>)$continuation).L$0 = null;
         ((<undefinedtype>)$continuation).label = 3;
         var10000 = getFeedListContent(userInfoContent, friendListContent, (Continuation)$continuation);
         if (var10000 == var6) {
            return var6;
         }
      }
      String var3 = (String)var10000;
      return Unit.INSTANCE;
   }
}

每一次函數(shù)調(diào)用的時候,continuation 都會作為最后一個參數(shù)傳到掛起函數(shù)里,Kotlin 編譯器幫我們做的,我們開發(fā)者是無感知。

2.CPS 返回值變化

final Object getUserInfoContent(@NotNull Continuation var0)
final Object getFriendListContent(@NotNull String var0, @NotNull Continuation var1)
final Object getFeedListContent(@NotNull String var0, @NotNull String var1, @NotNull Continuation var2)
suspend fun getUserInfoContent(): String {}
fun getUserInfoContent(cont: Continuation): Any? {}

經(jīng)過 CPS 轉(zhuǎn)換后,完整的函數(shù)簽名如下:

suspend fun getUserInfoContent(): String {}
fun getUserInfoContent(cont: Continuation<String>): Any? {}

Kotlin 編譯器的 CPS 轉(zhuǎn)換是等價的轉(zhuǎn)換。suspend () -> String 轉(zhuǎn)換成 (Continuation) -> Any?。

掛起函數(shù)經(jīng)過 CPS 轉(zhuǎn)換后,它的返回值有一個重要作用:標(biāo)志該掛起函數(shù)有沒有被掛起。

其實掛起函數(shù)也能不被掛起。

首先只要有 suspend 修飾的函數(shù),它就是掛起函數(shù)。

suspend fun getUserInfoContent(): String {
    withContext(Dispatchers.IO) {
        delay(1000L)
    }
    return "UserInfo"
}

執(zhí)行到 withContext{} 的時候,就會返回 CoroutineSingletons.COROUTINE_SUSPENDED 表示函數(shù)被掛起了。

下面的函數(shù)則是偽掛起函數(shù)

suspend fun getUserInfoContent2(): String {
    return "UserInfo"
}

因為它的方法體跟普通函數(shù)一樣。它跟一般的掛起函數(shù)有個區(qū)別:在執(zhí)行的時候,它并不會被掛起,因為它就是個普通函數(shù)。

二、掛起函數(shù)的反編譯

 @Nullable
   public static final Object fetchContent(@NotNull Continuation var0) {
      Object $continuation;
      label37: {
         if (var0 instanceof <undefinedtype>) {
            $continuation = (<undefinedtype>)var0;
            if ((((<undefinedtype>)$continuation).label & Integer.MIN_VALUE) != 0) {
               ((<undefinedtype>)$continuation).label -= Integer.MIN_VALUE;
               break label37;
            }
         }
         $continuation = new ContinuationImpl(var0) {
            // $FF: synthetic field
            Object result;
            int label;
            Object L$0;
            @Nullable
            public final Object invokeSuspend(@NotNull Object $result) {
               this.result = $result;
               this.label |= Integer.MIN_VALUE;
               return TestCoroutionKt.fetchContent(this);
            }
         };
      }
      Object var10000;
      label31: {
         String userInfoContent;
         Object var6;
         label30: {
            Object $result = ((<undefinedtype>)$continuation).result;
            var6 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
            switch(((<undefinedtype>)$continuation).label) {
            case 0:
               ResultKt.throwOnFailure($result);
               ((<undefinedtype>)$continuation).label = 1;
               var10000 = getUserInfoContent((Continuation)$continuation);
               if (var10000 == var6) {
                  return var6;
               }
               break;
            case 1:
               ResultKt.throwOnFailure($result);
               var10000 = $result;
               break;
            case 2:
               userInfoContent = (String)((<undefinedtype>)$continuation).L$0;
               ResultKt.throwOnFailure($result);
               var10000 = $result;
               break label30;
            case 3:
               ResultKt.throwOnFailure($result);
               var10000 = $result;
               break label31;
            default:
               throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
            }
            userInfoContent = (String)var10000;
            ((<undefinedtype>)$continuation).L$0 = userInfoContent;
            ((<undefinedtype>)$continuation).label = 2;
            var10000 = getFriendListContent(userInfoContent, (Continuation)$continuation);
            if (var10000 == var6) {
               return var6;
            }
         }
         String friendListContent = (String)var10000;
         ((<undefinedtype>)$continuation).L$0 = null;
         ((<undefinedtype>)$continuation).label = 3;
         var10000 = getFeedListContent(userInfoContent, friendListContent, (Continuation)$continuation);
         if (var10000 == var6) {
            return var6;
         }
      }
      String var3 = (String)var10000;
      return Unit.INSTANCE;
   }

label 是用來代表協(xié)程狀態(tài)機(jī)當(dāng)中狀態(tài);

result 是用來存儲當(dāng)前掛起函數(shù)執(zhí)行結(jié)果;

invokeSuspend 這個函數(shù),是整個狀態(tài)機(jī)的入口,它會將執(zhí)行流程轉(zhuǎn)交給 fetchContent 進(jìn)行再次調(diào)用;

userInfoContent, friendListContent用來存儲歷史掛起函數(shù)執(zhí)行結(jié)果。

if (var0 instanceof <undefinedtype>) {
            $continuation = (<undefinedtype>)var0;
            if ((((<undefinedtype>)$continuation).label & Integer.MIN_VALUE) != 0) {
               ((<undefinedtype>)$continuation).label -= Integer.MIN_VALUE;
               break label37;
            }
         }
  $continuation = new ContinuationImpl(var0) {
            // $FF: synthetic field
            Object result;
            int label;
            Object L$0;
            @Nullable
            public final Object invokeSuspend(@NotNull Object $result) {
               this.result = $result;
               this.label |= Integer.MIN_VALUE;
               return TestCoroutionKt.fetchContent(this);
            }
         };

invokeSuspend 最終會調(diào)用 fetchContent;

如果是初次運(yùn)行,會創(chuàng)建一個 ContinuationImpl對象,completion 作為參數(shù);這相當(dāng)于用一個新的 Continuation 包裝了舊的 Continuation;

如果不是初次運(yùn)行,直接將 completion 賦值給 continuation;這說明 continuation 在整個運(yùn)行期間,只會產(chǎn)生一個實例,這能極大地節(jié)省內(nèi)存開銷(對比 CallBack)。

// result 接收協(xié)程的運(yùn)行結(jié)果
var result = continuation.result
// suspendReturn 接收掛起函數(shù)的返回值
var suspendReturn: Any? = null
// CoroutineSingletons 是個枚舉類
// COROUTINE_SUSPENDED 代表當(dāng)前函數(shù)被掛起了
val sFlag = CoroutineSingletons.COROUTINE_SUSPENDED

continuation.label 是狀態(tài)流轉(zhuǎn)的關(guān)鍵,continuation.label 改變一次,就代表了掛起函數(shù)被調(diào)用了一次;每次掛起函數(shù)執(zhí)行完后,都會檢查是否發(fā)生異常;

fetchContent 里的原本的代碼,被拆分到狀態(tài)機(jī)里各個狀態(tài)中,分開執(zhí)行;getUserInfoContent(continuation)、getFriendListContent(user, continuation)、getFeedListContent(friendList, continuation) 三個函數(shù)調(diào)用的是同一個 continuation 實例;

var6 = IntrinsicsKt.getCOROUTINE_SUSPENDED();

如果一個函數(shù)被掛起了,它的返回值會是 CoroutineSingletons.COROUTINE_SUSPENDED;

在掛起函數(shù)執(zhí)行的過程中,狀態(tài)機(jī)會把之前的結(jié)果以成員變量的方式保存在 continuation 中。

本質(zhì)上來說,Kotlin 協(xié)程就是通過 label 代碼段嵌套,配合 switch 巧妙構(gòu)造出一個狀態(tài)機(jī)結(jié)構(gòu)。

三、Continuation

public interface Continuation<in T> {
    public val context: CoroutineContext
    public fun resumeWith(result: Result<T>)
}
@Suppress("WRONG_MODIFIER_TARGET")
public suspend inline val coroutineContext: CoroutineContext
    get() {
        throw NotImplementedError("Implemented as intrinsic")
    }

注意上面的suspend inline val coroutineContext,suspend 的這種用法只是一種特殊用法。它的作用:它是一個只有在掛起函數(shù)作用域下,才能訪問的頂層的不可變的變量。這里的 inline,意味著它的具體實現(xiàn)會被直接復(fù)制到代碼的調(diào)用處。

suspend fun testContext() = coroutineContext
@Nullable
   public static final Object testContext(@NotNull Continuation $completion) {
      return $completion.getContext();
   }

“suspend inline val coroutineContext”,本質(zhì)上就是 Kotlin 官方提供的一種方便開發(fā)者在掛起函數(shù)當(dāng)中,獲取協(xié)程上下文的手段。它的具體實現(xiàn),其實是 Kotlin 編譯器來完成的。

我們在掛起函數(shù)當(dāng)中無法直接訪問 Continuation 對象,但可以訪問到 Continuation 當(dāng)中的 coroutineContext。要知道,正常情況下,我們想要訪問 Continuation.coroutineContext,首先是要拿到 Continuation 對象的。但是,Kotlin 官方通過“suspend inline val coroutineContext”這個頂層變量,讓我們開發(fā)者能直接拿到 coroutineContext,卻對 Continuation 毫無感知。

掛起函數(shù)與 CoroutineContext 確實有著緊密的聯(lián)系。每個掛起函數(shù)當(dāng)中都會有 Continuation,而每個 Continuation 當(dāng)中都會有 coroutineContext。并且,我們在掛起函數(shù)當(dāng)中,就可以直接訪問當(dāng)前的 coroutineContext。

原文鏈接:https://blog.csdn.net/zhangying1994/article/details/127707821

欄目分類
最近更新