Kotlinコルーチンが公式バージョン1.3から削除されてから長い時間が経ちました。大勢の人にはおなじみです。AndroidのAACアーキテクチャからバックエンドまで見ることができます。その後、問題が発生します。ChengTi、方法を知っていますか作成されました?
ある日、同僚に尋ねました。コルーチンがどのように作成されたか知っていますか?
同僚:私が知っています、launch
、async
、...
。
私:?????
私:公式フレームワークを脇に置いて、Kotlin言語によって提供される最も基本的なAPIを使用するとどうなりますか?
同僚:????、次にヘアを作成します。
この時、王様を強制するニックネームをつけてくれた私は、チャンスがここにあることを知っていました。だからこの記事があります。
前提知識
まず、コルーチンとは何かを知る必要があります。基本的な概念を理解した後、それを見ることができます(知っている場合は、私が言わなかったとき)
良いショーが始まります
公式のフレームワークを確保し、言語の最も基本的なAPIのみを使用して、Kotlinコルーチンの内部設計に関する洞察を得ています。
分析を容易にするために、分析を容易にするために使用される概念とAPIを投稿します。
通常、コルーチンを開始する必要があります。
1.サスペンドの楽しみ
suspend fun foo() {}
复制代码
2.コルーチンを開始するためのAPI:startCoroutine、すぐに実行したくない場合は、次を使用できます:createCoroutine
@SinceKotlin("1.3")
@Suppress("UNCHECKED_CAST")
public fun <T> (suspend () -> T).startCoroutine(
completion: Continuation<T>
) {
createCoroutineUnintercepted(completion).intercepted().resume(Unit)
}
复制代码
@SinceKotlin("1.3")
@Suppress("UNCHECKED_CAST")
public fun <T> (suspend () -> T).createCoroutine(
completion: Continuation<T>
): Continuation<Unit> =
SafeContinuation(createCoroutineUnintercepted(completion).intercepted(), COROUTINE_SUSPENDED)
复制代码
上記のコルーチンの作成とコルーチンの開始では、非常に重要なメソッドがあることがわかりました。createCoroutineUnintercepted
メソッドは次のように実装されます(特定の関数については後で説明します。
@SinceKotlin("1.3")
public actual fun <T> (suspend () -> T).createCoroutineUnintercepted(
completion: Continuation<T>
): Continuation<Unit> {
val probeCompletion = probeCoroutineCreated(completion)
return if (this is BaseContinuationImpl)
create(probeCompletion)
else
createCoroutineFromSuspendFunction(probeCompletion) {
(this as Function1<Continuation<T>, Any?>).invoke(it)
}
}
复制代码
3.コルーチンが実行された後、コールバックするには完了が必要です。つまり、継続
@SinceKotlin("1.3")
public interface Continuation<in T> {
/**
* The context of the coroutine that corresponds to this continuation.
*/
public val context: CoroutineContext
/**
* Resumes the execution of the corresponding coroutine passing a successful or failed [result] as the
* return value of the last suspension point.
*/
public fun resumeWith(result: Result<T>)
}
复制代码
4、BaseContinuationImpl
@SinceKotlin("1.3")
internal abstract class BaseContinuationImpl(
// This is `public val` so that it is private on JVM and cannot be modified by untrusted code, yet
// it has a public getter (since even untrusted code is allowed to inspect its call stack).
public val completion: Continuation<Any?>?
) : Continuation<Any?>, CoroutineStackFrame, Serializable {
// This implementation is final. This fact is used to unroll resumeWith recursion.
public final override fun resumeWith(result: Result<Any?>) {
// This loop unrolls recursion in current.resumeWith(param) to make saner and shorter stack traces on resume
var current = this
var param = result
while (true) {
// Invoke "resume" debug probe on every resumed continuation, so that a debugging library infrastructure
// can precisely track what part of suspended callstack was already resumed
probeCoroutineResumed(current)
with(current) {
val completion = completion!! // fail fast when trying to resume continuation without completion
val outcome: Result<Any?> =
try {
val outcome = invokeSuspend(param)
if (outcome === COROUTINE_SUSPENDED) return
Result.success(outcome)
} catch (exception: Throwable) {
Result.failure(exception)
}
releaseIntercepted() // this state machine instance is terminating
if (completion is BaseContinuationImpl) {
// unrolling recursion via loop
current = completion
param = outcome
} else {
// top-level completion reached -- invoke and return
completion.resumeWith(outcome)
return
}
}
}
}
}
复制代码
5、ContinuationImpl
@SinceKotlin("1.3")
// State machines for named suspend functions extend from this class
internal abstract class ContinuationImpl(
completion: Continuation<Any?>?,
private val _context: CoroutineContext?
) : BaseContinuationImpl(completion) {
constructor(completion: Continuation<Any?>?) : this(completion, completion?.context)
public override val context: CoroutineContext
get() = _context!!
@Transient
private var intercepted: Continuation<Any?>? = null
public fun intercepted(): Continuation<Any?> =
intercepted
?: (context[ContinuationInterceptor]?.interceptContinuation(this) ?: this)
.also { intercepted = it }
protected override fun releaseIntercepted() {
val intercepted = intercepted
if (intercepted != null && intercepted !== this) {
context[ContinuationInterceptor]!!.releaseInterceptedContinuation(intercepted)
}
this.intercepted = CompletedContinuation // just in case
}
}
复制代码
6、SuspendLambda
@SinceKotlin("1.3")
// Suspension lambdas inherit from this class
internal abstract class SuspendLambda(
public override val arity: Int,
completion: Continuation<Any?>?
) : ContinuationImpl(completion), FunctionBase<Any?>, SuspendFunction {
constructor(arity: Int) : this(arity, null)
public override fun toString(): String =
if (completion == null)
Reflection.renderLambdaToString(this) // this is lambda
else
super.toString() // this is continuation
}
复制代码
7.SuspendLambdaの継承順序は-> SuspendLambda:-> ContinuationImpl:-> BaseContinuationImplです。
8. suspend()->ユニットはコンパイラによって匿名クラスにコンパイルされます。extendsSuspendLambdaはFunction1 <P、R>を実装し、2つの抽象メソッドinvokeSuspendとcreateを実装します。
9.サスペンドで装飾された関数コンパイラは、自動的に継続を追加します。
ミッドフィールドスタート
以前の理論では、コーディングしないのはいつも奇妙に感じます。
まず、コードを開始するための起動を定義します。
以前に見たものstartCoroutine
ではなく、コードが変更されたので、合理化した。これは完全に実行可能ですのでご安心ください。
fun <T> launch(block: suspend () -> T) {
val coroutine = block.createCoroutineUnintercepted(object : Continuation<T> {
override val context: CoroutineContext
get() = EmptyCoroutineContext
override fun resumeWith(result: Result<T>) {
println("result=$result")
}
})
coroutine.resume(Unit)
}
复制代码
次はサスペンド関数です(スレッドカットがないため、ここには実際のサスペンドはありません:
suspend fun loadImage() = suspendCoroutine<String> {
it.resume("https://www.baidu.com/img/PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png")
}
复制代码
全体的なコードを見てください:
import kotlin.coroutines.*
import kotlin.coroutines.intrinsics.createCoroutineUnintercepted
fun main() {
launch {
val image = loadImage()
println("image=$image")
}
}
fun <T> launch(block: suspend () -> T) {
val coroutine = block.createCoroutineUnintercepted(object : Continuation<T> {
override val context: CoroutineContext
get() = EmptyCoroutineContext
override fun resumeWith(result: Result<T>) {
println("resumeWith=$result")
}
})
coroutine.resume(Unit)
}
suspend fun loadImage() = suspendCoroutine<String> {
it.resume("https://www.baidu.com/img/PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png")
}
复制代码
OK、すべての準備ができたら、それを実行します。biu〜biu〜結果を参照してください:
[画像のアップロード...(image-1cca33-1612335597534-0)]
結果は完璧です。コルーチンがどのように作成されたか、resumeWithを呼び出す方法に興味があるかどうかはわかりません。基本的な学生は、コンパイラがサスペンドの楽しみに継続を追加することを知っているかもしれませんが、この継続をどのように呼び出すかはあまり明確ではありません。
さて、私は地獄に行かない、誰が地獄に行くのかという事実に沿って、以前の基礎と問題を踏まえて、コルーチン内で呼び出しプロセスを作成するものを分析するために段階的に見ていきましょう。
次に、コードを逆コンパイルして、実際にどのように見えるかを確認します。
public final class KoroutineKt {
public static final void main() {
launch(new KoroutineKt$main$1(null));
}
static final class KoroutineKt$main$1 extends SuspendLambda implements Function1<Continuation<? super Unit>, Object> {
int label;
@Nullable
public final Object invokeSuspend(@NotNull Object $result) {
String image;
Object object = IntrinsicsKt.getCOROUTINE_SUSPENDED();
switch (this.label) {
case 0:
this.label = 1;
if (KoroutineKt.loadImage((Continuation<? super String>) this) == object)
return object;
image = (String) KoroutineKt.loadImage((Continuation<? super String>) this);
System.out.println(image);
return Unit.INSTANCE;
case 1:
System.out.println(image);
return Unit.INSTANCE;
}
throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
}
KoroutineKt$main$1(Continuation param1Continuation) {
super(1, param1Continuation);
}
@NotNull
public final Continuation<Unit> create(@NotNull Continuation completion) {
Intrinsics.checkNotNullParameter(completion, "completion");
return (Continuation<Unit>) new KoroutineKt$main$1(completion);
}
public final Object invoke(Object param1Object) {
return ((KoroutineKt$main$1) create((Continuation) param1Object)).invokeSuspend(Unit.INSTANCE);
}
}
public static final <T> void launch(@NotNull Function1 block) {
Continuation coroutine = IntrinsicsKt.createCoroutineUnintercepted(block, new KoroutineKt$launch$coroutine$1());
Continuation intercepted = IntrinsicsKt.intercepted(coroutine);
Continuation continuation1 = intercepted;
Unit unit = Unit.INSTANCE;
Result.Companion companion = Result.Companion;
continuation1.resumeWith(Result.constructor - impl(unit));
}
public static final class KoroutineKt$launch$coroutine$1 implements Continuation<T> {
@NotNull
public CoroutineContext getContext() {
return (CoroutineContext) EmptyCoroutineContext.INSTANCE;
}
public void resumeWith(@NotNull Object result) {
String str = "result=" + Result.toString - impl(result);
System.out.println(str);
}
}
@Nullable
public static final Object loadImage(@NotNull Continuation completion) {
SafeContinuation safeContinuation = new SafeContinuation(IntrinsicsKt.intercepted($completion));
Continuation continuation = (Continuation) safeContinuation;
String str = "https://www.baidu.com/img/PCtm_d9c8750bed0b3c7d089fa7d55720d6cf.png";
Result.Companion companion = Result.Companion;
continuation.resumeWith(Result.constructor - impl(str));
if (safeContinuation.getOrThrow() == IntrinsicsKt.getCOROUTINE_SUSPENDED())
DebugProbesKt.probeCoroutineSuspended(completion);
return safeContinuation.getOrThrow();
}
}
复制代码
コードの信頼性を確保するために、デバッグコードの一部のみを削除しました。
逆コンパイルされたコードには、2つの匿名内部クラスがあります。
KoroutineKt$main$1
これはサスペンド()->ユニットです。前に述べたように、匿名の内部クラスにコンパイルされ、SuspendLambdaはFunction1 <P1、R>を実装します。
KoroutineKt$launch$coroutine$1
これが私たちの完成であり、目的です。継続ですが、それが私たちのサスペンドの楽しみであることを知っているので、あまり気にする必要はありません。最後にこれにコールバックします。
まず、launch()
メソッドを見てみましょう。
public static final<T> void launch(@NotNull Function1 block){
Continuation coroutine=IntrinsicsKt.createCoroutineUnintercepted(block,new KoroutineKt\$launch\$coroutine\$1());
Continuation intercepted=IntrinsicsKt.intercepted(coroutine);
Continuation continuation1=intercepted;
Unit unit=Unit.INSTANCE;
Result.Companion companion=Result.Companion;
continuation1.resumeWith(Result.constructor-impl(unit));
}
复制代码
launch()
最も重要な方法の1つは、createCoroutineUnintercepted()
コルーチンを作成することが最も重要なステップであるということです。
このmain()
メソッドでは、コンパイラーがnullの補完を含むKoroutineKtmainmainmain1を自動的に作成し、launch()
内部に入り、最初にcreateCoroutineUninterceptedを実行し、ここで補完を作成してから、ブロックと補完をcreateCoroutineUninterceptedメソッドに渡します。createCoroutineUninterceptedがそれがBaseContinuationImplの実装クラスであるかどうかを判断します。そうである場合は、createメソッドを呼び出します。そうでない場合は、Function.invokeを直接呼び出します。
いきなり理解しましたか?KoroutineKtmainmainmain1のcreateメソッドを見てみましょう。
@NotNull
public final Continuation<Unit> create(@NotNull Continuation completion) {
Intrinsics.checkNotNullParameter(completion, "completion");
return (Continuation<Unit>) new KoroutineKt\$main\$1(completion);
}
复制代码
create()
このメソッドは、完了を渡して新しいKoroutineKtmainmainmain1を作成し、1つを返しますContinuation<Unit>
。これまでのところ、コルーチンは作成されています。
コルーチンが作成されたら、開始する時間なので、継続はresumeWith(Unit)を呼び出してコルーチンを開始します。
ここでの継続はBaseContinuationImplから継承されているため、BaseContinuationImplのresumeWithをほぼ入力しました。
BaseContinuationImplのresumeWithが何をするのかを見て、コードに行きましょう。
public final override fun resumeWith(result: Result<Any?>) {
// This loop unrolls recursion in current.resumeWith(param) to make saner and shorter stack traces on resume
var current = this
var param = result
while (true) {
// Invoke "resume" debug probe on every resumed continuation, so that a debugging library infrastructure
// can precisely track what part of suspended callstack was already resumed
probeCoroutineResumed(current)
with(current) {
val completion = completion!! // fail fast when trying to resume continuation without completion
val outcome: Result<Any?> =
try {
val outcome = invokeSuspend(param)
if (outcome === COROUTINE_SUSPENDED) return
Result.success(outcome)
} catch (exception: Throwable) {
Result.failure(exception)
}
releaseIntercepted() // this state machine instance is terminating
if (completion is BaseContinuationImpl) {
// unrolling recursion via loop
current = completion
param = outcome
} else {
// top-level completion reached -- invoke and return
completion.resumeWith(outcome)
return
}
}
}
}
复制代码
resumeWithでは、と呼ばれるinvokeSuspend()
ので、私たちが書いたブロックにほぼ入ります。
public final Object invokeSuspend(@NotNull Object \$result) {
String image;
Object object = IntrinsicsKt.getCOROUTINE_SUSPENDED();
switch (this.label) {
case 0:
this.label = 1;
if (KoroutineKt.loadImage((Continuation<? super String>) this) == object)
return object;
image = (String) KoroutineKt.loadImage((Continuation<? super String>) this);
System.out.println(image);
return Unit.INSTANCE;
case 1:
System.out.println(image);
return Unit.INSTANCE;
}
throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
}
复制代码
これを見て、私たちがルシャンの素顔を通して見たと思いますか、なぜあなたはそれを言うのですか?
もう当たり前だと思います〜
まず、最初はラベルが0であり、切り替え後、ケース0、label = 1に入り、loadImageを実行します。loadImageが実際にネットワーク上の画像を要求するメソッドである場合(つまり、一時停止されている場合、戻り値はCOROUTINE_SUSPENDEDである必要があります。
上記のコードによると、COROUTINE_SUSPENDEDが返された場合は、invokeSuspendを直接返し、BaseContinuationImplのresumeWithでinvokeSuspendの戻りがCOROUTINE_SUSPENDEDであるかどうかを判断します。そうである場合は、returnはしばらくの間終了して、ループを終了します。データを作成し、結果を作成し、呼び出します。完了すると結果が返され、コルーチンは終了します。
この時点で、誰かが尋ねるかもしれません、どうやってすべてのリターンを切るのですか?
最初に、関数がサスペンドで変更された場合、コンパイラーは自動的に継続を追加すると言ったことを思い出してください。loadImageはサスペンド変更されたコンパイラーです。もちろん、その継続パラメーターを生成します。スイッチケース0では、次のようになります。すでに自己
パスloadImage、ああ、loadImageがデータを取得するまで待って、continuation.resumeWithを呼び出しますが、継続BaseContinuationImplを忘れないでください、resumeWithを呼び出して、実行後にinvokeSuspendをスイッチブランチに入れますが、loadImageの実装では以前は、ラベルは1であり、スイッチはケース1に入ります。この時点で、すでに画像データがあり、printlnを呼び出してデータを出力します。
さて、私たちのコルーチンの作成と呼び出しはここではほとんど明らかです。
パーフェクトエンディング
要約しましょう:
コルーチンの作成:
1.最初に、完了のない新しいSuspendLambdaは、2つの抽象メソッドinvokeSuspendとcreateを実装します。
2.コルーチンを作成するときは、completionを使用して、最初に作成されたSuspendLambdaのcreateメソッドを呼び出し、補完を使用してSuspendLambdaを新しく作成し、1を返しContinuation<Unit>
ます。
3. resumeWith(Unit)を呼び出して、コルーチンを開始します。
コルーチンの一時停止と再開:
1.コルーチンはフラグCOROUTINE_SUSPENDEDをハングし、しばらくの間終了します。
2.結果が戻ったら、resumeWithを呼び出してコルーチンを続行します。