Kotlin delay方法解析

本文记录了kotlin协程(Android)中delay方法的字节码实现,并解析了delay方法如何实现挂起操作。

一、delay方法介绍

1.1、delay方法使用举例

复制代码
class TestDelay {
    suspend fun testDelay() {
        Log.d("TestDelay", "before delay")
        delay(1000)
        Log.d("TestDelay", "after delay")
    }
}

上述方法将协程挂起1s,然后再恢复执行。

1.2、delay方法如何实现挂起操作

delay的挂起操作有别于Thread.sleep操作,delay挂起不会导致线程暂停执行,sleep会导致线程暂停执行。

查看上述代码的字节码反义结果如下:

delay方法正常执行情况下, 会返回一个COROUTINE_SUSPENDED对象,此对象与var4对象相同,会使当前的方法直接返回,从而起到挂起作用。

二、delay方法实现代码

接下查看delay是如何返回COROUTINE_SUSPENDED对象的

2.1、kotlin层代码

复制代码
public suspend fun delay(timeMillis: Long) {
    if (timeMillis <= 0) return // don't delay
    return suspendCancellableCoroutine sc@ { cont: CancellableContinuation<Unit> ->
        // if timeMillis == Long.MAX_VALUE then just wait forever like awaitCancellation, don't schedule.
        if (timeMillis < Long.MAX_VALUE) {
            cont.context.delay.scheduleResumeAfterDelay(timeMillis, cont)
        }
    }
}

public interface Delay {

    /** @suppress **/
    @Deprecated(
        message = "Deprecated without replacement as an internal method never intended for public use",
        level = DeprecationLevel.ERROR
    ) // Error since 1.6.0
    public suspend fun delay(time: Long) {
        if (time <= 0) return // don't delay
        return suspendCancellableCoroutine { scheduleResumeAfterDelay(time, it) }
    }

    public fun scheduleResumeAfterDelay(timeMillis: Long, continuation: CancellableContinuation<Unit>)

    public fun invokeOnTimeout(timeMillis: Long, block: Runnable, context: CoroutineContext): DisposableHandle =
        DefaultDelay.invokeOnTimeout(timeMillis, block, context)
}

查看协程代码(1.7.3版本)发现,delay方法会调用协程context中的scheduleResumeAfterDelay方法,此方法会等待一段时间,然后再resume协程。

这里出现一个问题----delay方法没有返回值,这与1.2中的方法好像有些冲突,在下一节查看delay方法的字节码,看下具体实现是如何,来确认是否真的有冲突。

这里先看下suspendCancellableCoroutine方法,其实现如下:

复制代码
public suspend inline fun <T> suspendCancellableCoroutine(
    crossinline block: (CancellableContinuation<T>) -> Unit
): T =
    suspendCoroutineUninterceptedOrReturn { uCont ->
        val cancellable = CancellableContinuationImpl(uCont.intercepted(), resumeMode = MODE_CANCELLABLE)
        /*
         * For non-atomic cancellation we setup parent-child relationship immediately
         * in case when `block` blocks the current thread (e.g. Rx2 with trampoline scheduler), but
         * properly supports cancellation.
         */
        cancellable.initCancellability()
        block(cancellable)
        cancellable.getResult()
    }


@SinceKotlin("1.3")
@InlineOnly
@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")
}

上图中的2个方法都用inline标注了,是内敛方法,所以在编译时会直接编译到delay方法中(注意:因为直接编译到delay方法中,所以内敛方法中最后一行的结果会被delay方法中的return使用)。同时看到在方法的最后调用了cancellable.getResult()方法,经查看代码发现,此方法内会返回COROUTINE_SUSPENDED对象。在此我们猜测此对象会作为delay方法的返回值,在下一节也会印证此猜测。

这样就了解了delay方法(编译后的字节码方法)的返回值对象是由何而来。针对kotlin源码中delay方法并没有返回值的问题,猜测在编译时delay方法会被编译成一个有返回值的方法,在下一节也会印证此猜测。

2.2、字节码

delay方法的字节码反义如下

复制代码
  @Nullable
  public static final Object delay(long timeMillis, @NotNull Continuation $completion) {
    if (timeMillis <= 0L)
      return Unit.INSTANCE; 
    int $i$f$suspendCancellableCoroutine = 0;
    Continuation uCont$iv = $completion;
    int $i$a$-suspendCoroutineUninterceptedOrReturn-CancellableContinuationKt$suspendCancellableCoroutine$2$iv = 0;
    CancellableContinuationImpl<? super Unit> cancellable$iv = new CancellableContinuationImpl(IntrinsicsKt.intercepted(uCont$iv), 1);
    cancellable$iv.initCancellability();
    CancellableContinuation<? super Unit> cont = cancellable$iv;
    int $i$a$-suspendCancellableCoroutine-DelayKt$delay$2 = 0;
    if (timeMillis < Long.MAX_VALUE)
      getDelay(cont.getContext()).scheduleResumeAfterDelay(timeMillis, cont); 
    if (cancellable$iv.getResult() == IntrinsicsKt.getCOROUTINE_SUSPENDED())
      DebugProbesKt.probeCoroutineSuspended($completion); 
    if (cancellable$iv.getResult() == 
      IntrinsicsKt.getCOROUTINE_SUSPENDED())
      return cancellable$iv.getResult(); 
    cancellable$iv.getResult();
    return Unit.INSTANCE;
  }

可以看出delay方法在经过编译之后,会变成一个返回值为object的方法,同时在正常执行情况下,会将cancellable$iv.getResult()作为返回值,而这个方法也就是kotlin中的cancellable.getResult()方法。

三、总结

本文介绍了delay方法会被编译成一个有返回值的方法,并通过返回COROUTINE_SUSPENDED对象的方式,实现挂起。在挂起后,会等待一段时间,然后再调用协程的resume方法,恢复协程执行。此恢复操作,如果是在主线程会通过handler.postDelay方式实现;如果是在子线程,会通过一个DelayedTask实现,在此不进行具体分析。

相关推荐
海洋与大气科学2 分钟前
[matlab]南海地形眩晕图代码
开发语言·matlab
IvanCodes4 分钟前
MySQL 视图
android·数据库·sql·mysql·oracle
KevinWang_9 分钟前
Java 和 Kotlin 混编导致的 bug
android·kotlin
中国lanwp10 分钟前
Spring Boot 版本与对应 JDK 版本兼容性
java·开发语言·spring boot
码银15 分钟前
【Java】接口interface学习
java·开发语言·学习
好学人23 分钟前
Android动画系统全面解析
android
leverge200926 分钟前
android studio 运行java main报错
android·ide·android studio
RichardLai8827 分钟前
Flutter 环境搭建
android·flutter
DKPT28 分钟前
重构之去除多余的if-else
java·开发语言·笔记·学习·面试
思想觉悟36 分钟前
ubuntu编译android12源码
android·ubuntu·源码