Ali P7 ボスが Kotlin コルーチンの実装原理を教えてくれます: サスペンドとレジューム

Kotlin コルーチンの実装原理

Kotlin今日はコルーチンについてお話しますCoroutine

コルーチンに触れたことがあれば、次のような疑問があると思います。

  1. コルーチンとは正確には何ですか?
  2. コルーチンの役割suspendと動作原理は何ですか?
  3. コルーチン内のいくつかのキー名 ( 、JobCoroutineDispatcherなどCoroutineContext)CoroutineScopeの関係は何ですか?
  4. コルーチンのいわゆる非ブロッキング中断および回復とは何ですか?
  5. コルーチンの内部実装原則は何ですか?

次のいくつかの記事では、これらの質問を分析しようとします。誰もが議論に参加することを歓迎します。

電話を切る

コルーチンはノンブロッキング サスペンションを使用して、コルーチンが確実に実行されるようにします。ノンブロッキングサスペンドとは?電話を切るとはどのような操作なのかお話しましょう。

キーワードについては前回の記事で触れましたがsuspend、その機能の 1 つは、コードが呼び出されると、Continuation型パラメーターがメソッドに追加され、コルーチンでの上下の転送が確実に行われるようにすることですContinuaton

もう 1 つの重要な役割は、中断されたコルーチンを識別することです。

コルーチンの実行中に変更されたメソッドが検出されると現在のコルーチンが中断される場合があります。suspend

注意が可能です。

メソッドは自由に記述でき、メソッドをsuspend変更することもできますが、このメソッドはコルーチンで呼び出されたときに中断されません。例えば

private suspend fun a() {
 println("aa")
}

lifecycleScope.launch {
 a()
}

COROUTINE_SUSPENDEDこのメソッドは型を返さないためです。

コルーチンが中断されているというフラグは、対応する state の return フラグですCOROUTINE_SUSPENDED

もう少し深く掘り下げるには、ステート マシンが関係します。コルーチンの内部では、コルーチンの各中断ポイントを管理するためにステート マシンが使用されます。

テキストは少し抽象的です。コードを具体的に見てみましょう。上記のa方法を例として説明します。

最初にAndroid Studioこのコードを開きますKotlin Bytecodeで開くことができますTools -> Kotlin -> Show Kotlin Bytecode

次に、Decompileオプションの 1 つをクリックして、対応する逆コンパイルされたjavaコードを生成します。最終的なコードは次のとおりです。

BuildersKt.launch$default((CoroutineScope)LifecycleOwnerKt.getLifecycleScope(this), (CoroutineContext)null, (CoroutineStart)null, (Function2)(new Function2((Continuation)null) {
   private CoroutineScope p$;
   Object L$0;
   int label;
 
   @Nullable
   public final Object invokeSuspend(@NotNull Object $result) {
      // 挂起标识
      Object var3 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
      CoroutineScope $this$launch;
      switch(this.label) {
      case 0:
         ResultKt.throwOnFailure($result);
         $this$launch = this.p$;
         MainActivity var10000 = MainActivity.this;
         // 保存现场
         this.L$0 = $this$launch;
         // 设置挂起后恢复时,进入的状态
         this.label = 1;
         // 判断是否挂起
         if (var10000.a(this) == var3) {
            // 挂起,跳出该方法
            return var3;
         }
         // 不需要挂起,协程继续执行其他逻辑
         break;
      case 1:
         // 恢复现场
         $this$launch = (CoroutineScope)this.L$0;
         // 是否需要抛出异常
         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.checkParameterIsNotNull(completion, "completion");
      Function2 var3 = new <anonymous constructor>(completion);
      var3.p$ = (CoroutineScope)value;
      return var3;
   }
 
   public final Object invoke(Object var1, Object var2) {
      return ((<undefinedtype>)this.create(var1, (Continuation)var2)).invokeSuspend(Unit.INSTANCE);
   }
}), 3, (Object)null);

上記のコードはコルーチンのステートマシンであり、label異なる状態を表すために使用され、case対応する異なるロジックコードを実行します。

前回の記事で述べたように、コルーチンが開始されると、resumeWithメソッドが手動で 1 回呼び出され、それに対応する内部ロジックが上記のinvokeSuspendメソッドを実行します。

したがって、コルーチンが初めて実行されるとき、label値は であり0case 0:ステートメントに入ります。このとき、記録サイトは、一時停止される可能性のある状態に備え、次に実行される可能性のある状態を設定します。

aメソッドが値を返す場合var3、これはvar3それに対応しますCOROUTINE_SUSPENDEDaしたがって、内部ステートメントはメソッドが返されたときにのみCOROUTINE_SUSPENDED実行されif、メソッドは飛び出し、この時点でコルーチンは中断されます。現在のスレッドは他のロジックも実行でき、コルーチンの中断によってブロックされることはありません。

したがって、コードレベルでは、コルーチンの一時停止は、コルーチン実行のメソッド本体から飛び出すか、コルーチンの現在のステートマシンの下で対応する状態から飛び出し、次の状態が来るのを待つことです。そしてそれを実行します。

では、なぜ私たちが書いたメソッドがa中断されないと言うのでしょうか?

@Nullable
final Object a(@NotNull Continuation $completion) {
   return Unit.INSTANCE;
}

その戻り値は ではないことがわかりましたCOROUTINE_SUSPENDED

停止しないということは、どのような場合に停止するのでしょうか?

メソッドにメソッドをa入れると、中断されます。delay

@Nullable
final Object a(@NotNull Continuation $completion) {
   Object var10000 = DelayKt.delay(1000L, $completion);
   return var10000 == IntrinsicsKt.getCOROUTINE_SUSPENDED() ? var10000 : Unit.INSTANCE;
}

メソッドは内部的にメソッドを呼び出している間にそれ自体を作成するdelayため、実際に hang をトリガーするのはメソッドですdelayContinuationgetResult

 internal fun getResult(): Any? {
     installParentCancellationHandler()
     if (trySuspend()) return COROUTINE_SUSPENDED
     // otherwise, onCompletionInternal was already invoked & invoked tryResume, and the result is in the state
     val state = this.state
     if (state is CompletedExceptionally) throw recoverStackTrace(state.cause, this)
     return getSuccessfulResult(state)
 }

getResultメソッドでは、trySuspend現在のコルーチンを中断すると判断されます。親クラスのコルーチンの一時停止は、それ自体のコルーチンを一時停止することによってトリガーされます。

テストだけの場合は、aメソッドを直接返すことができますCOROUTINE_SUSPENDED

 private suspend fun a(): Any {
     return kotlin.coroutines.intrinsics.COROUTINE_SUSPENDED
 }

もちろん、このようにオンラインで書いてはいけません。コルーチンをこのように書いたら、再開することができないため、常に中断されます。

回復

それでは、コルーチンの回復について話しましょう。

コルーチンのリカバリー エッセンスは、メソッドによってトリガーされますContinuationresumeWith

一時停止できる例を見て、それを使用して、コルーチンの一時停止と回復のプロセス全体を分析してみましょう。

println("main start")
lifecycleScope.launch {
   println("async start")
   val b = async {
       delay(2000)
       "async"
   }
   b.await()
   println("async end")
}
Handler().postDelayed({
   println("main end")
}, 1000)

Kotlinコードは非常に単純で、現在のコルーチンはメイン スレッドで実行され、コルーチンの一時停止をトリガーするasyncメソッドが内部で実行されます。await

java対応する逆コンパイルされたコードを見てみましょう

// 1
String var2 = "main start";
System.out.println(var2);
BuildersKt.launch$default((CoroutineScope)LifecycleOwnerKt.getLifecycleScope(this), (CoroutineContext)null, (CoroutineStart)null, (Function2)(new Function2((Continuation)null) {
   private CoroutineScope p$;
   Object L$0;
   Object L$1;
   int label;
 
   @Nullable
   public final Object invokeSuspend(@NotNull Object $result) {
      Object var5 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
      CoroutineScope $this$launch;
      Deferred b;
      switch(this.label) {
      case 0:
         // 2
         ResultKt.throwOnFailure($result);
         $this$launch = this.p$;
         String var6 = "async start";
         System.out.println(var6);
         b = BuildersKt.async$default($this$launch, (CoroutineContext)null, (CoroutineStart)null, (Function2)(new Function2((Continuation)null) {
            private CoroutineScope p$;
            Object L$0;
            int label;
 
            @Nullable
            public final Object invokeSuspend(@NotNull Object $result) {
               Object var3 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
               CoroutineScope $this$async;
               switch(this.label) {
               case 0:
                  // 3
                  ResultKt.throwOnFailure($result);
                  $this$async = this.p$;
                  this.L$0 = $this$async;
                  this.label = 1;
                  if (DelayKt.delay(2000L, this) == var3) {
                     return var3;
                  }
                  break;
               case 1:
                  // 5、6
                  $this$async = (CoroutineScope)this.L$0;
                  ResultKt.throwOnFailure($result);
                  break;
               default:
                  throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
               }
 
               return "async";
            }
 
            @NotNull
            public final Continuation create(@Nullable Object value, @NotNull Continuation completion) {
               Intrinsics.checkParameterIsNotNull(completion, "completion");
               Function2 var3 = new <anonymous constructor>(completion);
               var3.p$ = (CoroutineScope)value;
               return var3;
            }
 
            public final Object invoke(Object var1, Object var2) {
               return ((<undefinedtype>)this.create(var1, (Continuation)var2)).invokeSuspend(Unit.INSTANCE);
            }
         }), 3, (Object)null);
         this.L$0 = $this$launch;
         this.L$1 = b;
         this.label = 1;
         if (b.await(this) == var5) {
            return var5;
         }
         break;
      case 1:
         // 7
         b = (Deferred)this.L$1;
         $this$launch = (CoroutineScope)this.L$0;
         ResultKt.throwOnFailure($result);
         break;
      default:
         throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
      }
 
      // 8
      String var4 = "async end";
      System.out.println(var4);
      return Unit.INSTANCE;
   }
 
   @NotNull
   public final Continuation create(@Nullable Object value, @NotNull Continuation completion) {
      Intrinsics.checkParameterIsNotNull(completion, "completion");
      Function2 var3 = new <anonymous constructor>(completion);
      var3.p$ = (CoroutineScope)value;
      return var3;
   }
 
   public final Object invoke(Object var1, Object var2) {
      return ((<undefinedtype>)this.create(var1, (Continuation)var2)).invokeSuspend(Unit.INSTANCE);
   }
}), 3, (Object)null);
// 4
(new Handler()).postDelayed((Runnable)null.INSTANCE, 1000L);

少し長いですが、重要なポイントとそのステート マシンに関連する内容だけを見ていきます。

  1. 最初に出力しmain start次にlaunchコルーチンを作成してコルーチン ステート マシンに入り、この時点でlabel関連するロジック0を実行します。case: 0
  2. 、 output 、 call 、および passcase: 0を入力し現在のコルーチンを中断し、中断プロセス中に現在の中断ポイントのデータを記録し、に設定します**async start**asyncawaitlable1
  3. 作成したコルーチンを入力しasync、このときasyncコルーチンにlable0実行async case: 0dealy中断するasyncコルーチンを入力します。labelに設定します1待って目が覚めた2s
  4. この時点で、コルーチンはすべて中断されます。つまり、コルーチン メソッドから飛び出しlaunchhandler操作を実行します。post 1sコルーチンよりもdealy短いので、最初に出力し**main end**、それを通過して1s回復コルーチンの段階に入ります
  5. asyncThe coroutine in is delayresumed.のオブジェクトdelaymethod に渡されることに注意してくださいthisしたがって、内部タイミングが完了すると、メソッドが呼び出されてコルーチン in が再開されますつまり、メソッドが呼び出されますasyncContinuationdelay2sContinuationresumeWithasyncinvokeSuspend
  6. 中断される前にasync labelに設定されているため1、 に入りcase: 1、以前に中断されたサイトを再開し、例外をチェックして、最後に を返しますasync
  7. このときawait, 一時停止ポイントが復元されます. これも に渡されthis, 対応するものはlaunch途中にあることに注意してくださいContinuation. メソッドはコールバックされます.そして最後にresumeWith呼び出されます.マシーン。invokeSuspendcase 1:
  8. 最後に、引き続きoutputasync endを実行すると、コルーチン操作が終了します。

上記のコードを実行して、出力が正しいことを確認できます

main start
async start
main end
async end

要約すると、コルーチンはsuspend一時停止ポイントを識別するために使用されますが、実際の一時停止ポイントはCOROUTINE_SUSPENDEDそれが戻るかどうかによって判断する必要があり、コードはステート マシンを介してコルーチンの一時停止と回復を反映します。中断する必要がある場合は、まずシーンを保持して次のステート ポイントを設定してから、メソッドを終了してコルーチンを中断します。中断中、現在のスレッドはブロックされません。対応するリカバリ パスがresumeWithステート マシンの次の状態に入ると同時に、次の状態に入るときに以前に中断されたシーンが再開されます。

この記事では、主にコルーチンの中断と再開の原理を紹介し、コルーチンのステート マシンに関連する実行プロセスについても分析します。コルーチンを学んでいる方の参考になれば幸いです。今後のコルーチン解析にご期待ください。

さらに Kotlin 学習教材が必要な場合は、QR コードをスキャンして無料で入手できます。

史上最も詳細な Android 版 kotlin コルーチン エントリー アドバンスド コンバット

第 1 章 Kotlin コルーチンの基本の紹介

● コルーチンとは

● Job、Deferred、コルーチンのスコープとは

● Kotlin コルーチンの基本的な使い方

画像

第2章 Kotlinコルーチンの重要な知識ポイントの予備説明

● コルーチンスケジューラ

● コルーチンのコンテキスト

●コルーチン起動モード

● コルーチンのスコープ

●サスペンド機能

画像

第3章 Kotlin コルーチンの例外処理

●コルーチン例外の発生過程

● コルーチンの例外処理

画像

Chapter 4 Androidにおけるkotlinコルーチンの基本的な応用

おすすめ

転載: blog.csdn.net/Android_XG/article/details/129729479