首頁 > 軟體

Kotlin協程基礎元素梳理分析

2022-11-23 14:01:21

Kotlin 協程的基礎元素:Continuation、SafeContinuation、CoroutineContext、CombinedContext、CancellationException、intrinsics。CombinedContext是 CoroutineContext 的一個實現類,SafeContinuation是 Continuation 的實現類。

Continuation 是什麼?

class Call<T>(callBack: Call.CallBack<T>) {
    fun cancel() {
    }
    interface CallBack<T> {
        fun onSuccess(data: T)
        fun onFail(throwable: Throwable)
    }
}
suspend fun <T : Any> Call<T>.await(): T =
    suspendCancellableCoroutine<T> { continuation ->
        val call = Call(object : Call.CallBack<T> {
            override fun onSuccess(data: T) {
                continuation.resume(data, onCancellation = null)
            }
            override fun onFail(throwable: Throwable) {
                continuation.resumeWithException(throwable)
            }
        })
        continuation.invokeOnCancellation {
            call.cancel()
        }
    }

要實現掛起函數的時候,可以使用 suspendCoroutine{}、suspendCancellableCoroutine{}這兩個高階函數,在它們的 Lambda 當中,我們可以使用它暴露出來的 continuation 物件,把程式的執行結果或異常傳到外部去。常用於實現掛起函數內部邏輯。

fun checkLength() {
    runBlocking {
        val result = getStrLengthSuspend("Kotlin")
        println(result)
    }
}
suspend fun getStrLengthSuspend(text:String):Int = suspendCoroutine {
    continuation ->
    thread {
        Thread.sleep(1000L)
        continuation.resume(text.length)
    }
}
Log
6
Process finished with exit code 0

使用 suspendCoroutine{}實現了掛起函數,在它內部,使用 continuation.resume() 的方式,傳出了掛起函數的返回值。

為什麼以 continuation.resume() 這樣非同步的方式傳出結果,掛起函數就能接收到結果呢?

suspend fun getStrLengthSuspend(text: String): Int = suspendCoroutine { continuation ->
    thread {
        Thread.sleep(1000L)
        continuation.resume(text.length)
    }
}
fun checkLength2() {
    val func = ::getStrLengthSuspend as (String, Continuation<Int>) -> Any?
    func("Kotlin", object :Continuation<Int> {
        override val context: CoroutineContext
            get() = EmptyCoroutineContext
 
        override fun resumeWith(result: Result<Int>) {
            println(result.getOrNull())
        }
    })
    Thread.sleep(2000L)
}
Log
6
Process finished with exit code 0

把函數強轉成了帶有 Continuation 的函數型別,然後通過匿名內部類的方式,建立了一個 Continuation 物件傳了進去。掛起函數的本質,就是 Callback!

把 Continuation 改為 Callback:

fun getStrLength() {
    func2("Kotlin", object : MyCallBack<Int> {
        override fun resume(value: Int) {
            println(value)
        }
    })
}
fun func2(text: String, callBack: MyCallBack<Int>) {
    thread {
        Thread.sleep(1000L)
        callBack.resume(text.length)
    }
}
interface MyCallBack<T> {
    fun resume(value: T)
}
Log
6
Process finished with exit code 0

Continuation 改成 Callback 以後,使用匿名內部類建立 Callback 用於接收非同步結果;非同步函數內部,使用 callback.resume() 將結果傳出去。

Kotlin 協程當中的 Continuation,作用其實就相當於 Callback,它既可以用於實現掛起函數,往掛起函數的外部傳遞結果;也可以用於呼叫掛起函數,我們可以建立 Continuation 的匿名內部類,來接收掛起函數傳遞出來的結果。

Java 程式碼中如何呼叫 Kotlin 的掛起函數嗎?

  public static void main(String[] args) {
        SuspendFromJavaDo.INSTANCE.getUserInfo(new Continuation<String>() {
            @NonNull
            @Override
            public CoroutineContext getContext() {
                return EmptyCoroutineContext.INSTANCE;
            }
            @Override
            public void resumeWith(@NonNull Object o) {
                System.out.println(o + "");
            }
        });
    }
object SuspendFromJavaDo {
    suspend fun getUserInfo():String {
        delay(1000L)
        return "Kotlin"
    }
}

所以,實現掛起函數邏輯的時候,常用到 suspendCoroutine{}、suspendCancellableCoroutine{}。

Continuation.kt原始碼

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>)
}
public suspend inline fun <T> suspendCoroutine(crossinline block: (Continuation<T>) -> Unit): T {
    contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) }
    return suspendCoroutineUninterceptedOrReturn { c: Continuation<T> ->
        val safe = SafeContinuation(c.intercepted())
        block(safe)
        safe.getOrThrow()
    }
}

suspendCoroutineUninterceptedOrReturn{}實現了suspendCoroutine{}。

val safe = SafeContinuation(c.intercepted()) 包裹Continuation。

block(safe)呼叫 Lambda 當中的邏輯。

safe.getOrThrow() 取出 block(safe) 的執行結果,Continuation 當中是可以儲存 result 的。這個 Result 可能是正確的結果,也可能是異常。

@Suppress("UNUSED_PARAMETER", "RedundantSuspendModifier")
public suspend inline fun <T> suspendCoroutineUninterceptedOrReturn(crossinline block: (Continuation<T>) -> Any?): T {
    contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) }
    throw NotImplementedError("Implementation of suspendCoroutineUninterceptedOrReturn is intrinsic")
}

為什麼這個高階函數的原始碼會是丟擲異常呢?

“Implementation of suspendCoroutineUninterceptedOrReturn is intrinsic.”suspendCoroutineUninterceptedOrReturn 是一個編譯器內建函數,它是由 Kotlin 編譯器來實現的。

suspendCoroutineUninterceptedOrReturn 這個高階函數的引數,它會接收一個 Lambda,型別是(Continuation<T>) -> Any?,這裡的“Any?”型別,其實就能代表當前這個掛起函數是否真正掛起。

fun test() {
    runBlocking {
        val result = testNoSuspendCoroutinue()
        println(result)
    }
}
private suspend fun testNoSuspendCoroutinue() = suspendCoroutineUninterceptedOrReturn<String>{
    continuation ->
    return@suspendCoroutineUninterceptedOrReturn "Kotlin"
}
Log
Kotlin
Process finished with exit code 0

直接使用 suspendCoroutineUninterceptedOrReturn 實現了掛起函數,並且,在它的 Lambda 當中,我們並沒有呼叫 continuation.resume()。在掛起函數的外部確實也可以接收到這個結果。

 private static final Object testNoSuspendCoroutinue(Continuation $completion) {
      int var2 = false;
      if ("Kotlin" == IntrinsicsKt.getCOROUTINE_SUSPENDED()) {
         DebugProbesKt.probeCoroutineSuspended($completion);
      }
      return "Kotlin";
   }

這個函數其實就是一個偽掛起函數,它的內部並不會真正掛起。這樣,當我們從外部呼叫這個函數的時候,這個函數會立即返回結果。

private suspend fun testSuspendCoroutinues() =
    suspendCoroutineUninterceptedOrReturn<String> { continuation ->
        thread {
            Thread.sleep(2000L)
            continuation.resume("Kotlin")
        }
        return@suspendCoroutineUninterceptedOrReturn kotlin.coroutines.intrinsics.COROUTINE_SUSPENDED
    }
fun main() {
    runBlocking {
        val testSuspendCoroutinues = testSuspendCoroutinues()
        println(testSuspendCoroutinues)
    }
}
Kotlin
Process finished with exit code 0

使用了 continuation.resume(),掛起函數的外部也能接收到這個結果。

   private static final Object testSuspendCoroutinues(Continuation $completion) {
      int var2 = false;
      ThreadsKt.thread$default(false, false, (ClassLoader)null, (String)null, 0, (Function0)(new TestCoroutine777Kt$testSuspendCoroutinues$2$1($completion)), 31, (Object)null);
      Object var10000 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
        //將 var10000 賦值為 COROUTINE_SUSPENDED 這個掛起標誌位。
      if (var10000 == IntrinsicsKt.getCOROUTINE_SUSPENDED()) {
         DebugProbesKt.probeCoroutineSuspended($completion);
      }
//返回掛起標誌位,代表 testSuspendCoroutinues() 這個函數會真正掛起。
      return var10000;
   }
BuildersKt.runBlocking$default((CoroutineContext)null, (Function2)(new Function2((Continuation)null) {
         int label;
         @Nullable
         public final Object invokeSuspend(@NotNull Object $result) {
            Object var3 = IntrinsicsKt.getCOROUTINE_SUSPENDED();
            Object var10000;
            switch(this.label) {
            case 0:
               ResultKt.throwOnFailure($result);
               this.label = 1;
               var10000 = TestCoroutine777Kt.testSuspendCoroutinues(this);
               if (var10000 == var3) {
                  return var3;
               }
               break;
            case 1:
               ResultKt.throwOnFailure($result);
               var10000 = $result;
               break;
            default:
               throw new IllegalStateException("call to 'resume' before 'invoke' with coroutine");
            }
            String testSuspendCoroutinues = (String)var10000;
            System.out.println(testSuspendCoroutinues);
            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);
         }
      }), 1, (Object)null);

由於 suspend 修飾的函數,既可能返回 CoroutineSingletons.COROUTINE_SUSPENDED,也可能返回實際結果,甚至可能返回 null,為了適配所有的可能性,CPS 轉換後的函數返回值型別就只能是 Any? 了。

suspendCoroutineUninterceptedOrReturn{}這個高階函數的作用了:它可以將掛起函數當中的 Continuation 以引數的形式暴露出來,在它的 Lambda 當中,我們可以直接返回結果,這時候它就是一個“偽掛起函數”;或者,我們也可以返回 COROUTINE_SUSPENDED 這個掛起標誌位,然後使用 continuation.resume() 傳遞結果。相應的,suspendCoroutine{}、suspendCancellableCoroutine{}這兩個高階函數,只是對它的一種封裝而已。

到此這篇關於Kotlin協程基礎元素梳理分析的文章就介紹到這了,更多相關Kotlin協程基礎元素內容請搜尋it145.com以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援it145.com!


IT145.com E-mail:sddin#qq.com